New modules.
This commit is contained in:
parent
1a08b3adf0
commit
236d69c2d7
1151
deps/cjson/cJSON.c
vendored
Normal file
1151
deps/cjson/cJSON.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
162
deps/cjson/cJSON.h
vendored
Normal file
162
deps/cjson/cJSON.h
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7)
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
typedef struct cJSON
|
||||
{
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
struct cJSON *child;
|
||||
int type;
|
||||
char *valuestring;
|
||||
int valueint;
|
||||
double valuedouble;
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
void *(*malloc_fn)(size_t sz);
|
||||
void (*free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
extern void cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||
|
||||
extern cJSON *cJSON_Parse(const char *value);
|
||||
extern cJSON *cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||
extern char *cJSON_Print(const cJSON *item);
|
||||
extern char *cJSON_PrintUnformatted(const cJSON *item);
|
||||
extern char *cJSON_PrintBuffered(const cJSON *item, int prebuffer, int fmt);
|
||||
extern void cJSON_Delete(cJSON *item);
|
||||
|
||||
extern int cJSON_GetArraySize(const cJSON *array);
|
||||
extern cJSON *cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
extern cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string);
|
||||
extern cJSON *cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string);
|
||||
extern int cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
|
||||
extern const char *cJSON_GetErrorPtr(void);
|
||||
|
||||
extern char *cJSON_GetStringValue(const cJSON *item);
|
||||
extern double cJSON_GetNumberValue(const cJSON *item);
|
||||
|
||||
extern int cJSON_IsInvalid(const cJSON *item);
|
||||
extern int cJSON_IsFalse(const cJSON *item);
|
||||
extern int cJSON_IsTrue(const cJSON *item);
|
||||
extern int cJSON_IsBool(const cJSON *item);
|
||||
extern int cJSON_IsNull(const cJSON *item);
|
||||
extern int cJSON_IsNumber(const cJSON *item);
|
||||
extern int cJSON_IsString(const cJSON *item);
|
||||
extern int cJSON_IsArray(const cJSON *item);
|
||||
extern int cJSON_IsObject(const cJSON *item);
|
||||
extern int cJSON_IsRaw(const cJSON *item);
|
||||
|
||||
extern cJSON *cJSON_CreateNull(void);
|
||||
extern cJSON *cJSON_CreateTrue(void);
|
||||
extern cJSON *cJSON_CreateFalse(void);
|
||||
extern cJSON *cJSON_CreateBool(int boolean);
|
||||
extern cJSON *cJSON_CreateNumber(double num);
|
||||
extern cJSON *cJSON_CreateString(const char *string);
|
||||
extern cJSON *cJSON_CreateRaw(const char *raw);
|
||||
extern cJSON *cJSON_CreateArray(void);
|
||||
extern cJSON *cJSON_CreateObject(void);
|
||||
|
||||
extern cJSON *cJSON_CreateStringReference(const char *string);
|
||||
extern cJSON *cJSON_CreateObjectReference(const cJSON *child);
|
||||
extern cJSON *cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
extern cJSON *cJSON_CreateIntArray(const int *numbers, int count);
|
||||
extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
extern cJSON *cJSON_CreateStringArray(const char *const *strings, int count);
|
||||
|
||||
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
extern void cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
extern void cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
extern cJSON *cJSON_DetachItemViaPointer(cJSON *parent, cJSON *item);
|
||||
extern cJSON *cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
extern void cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
extern cJSON *cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
extern cJSON *cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
extern void cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
extern void cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
extern int cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
extern int cJSON_ReplaceItemViaPointer(cJSON *parent, cJSON *item, cJSON *replacement);
|
||||
extern int cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
extern int cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem);
|
||||
extern int cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem);
|
||||
|
||||
extern cJSON *cJSON_Duplicate(const cJSON *item, int recurse);
|
||||
extern int cJSON_Compare(const cJSON *a, const cJSON *b, int case_sensitive);
|
||||
|
||||
extern void cJSON_Minify(char *json);
|
||||
|
||||
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
|
||||
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
|
||||
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
|
||||
#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
|
||||
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
|
||||
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
|
||||
#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s))
|
||||
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
#define cJSON_SetNumberValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
#define cJSON_SetValuestring(object, string) ((object) ? (object)->valuestring = (string) : (string))
|
||||
|
||||
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
extern void *cJSON_malloc(size_t size);
|
||||
extern void cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
29636
deps/sqlite/shell.c
vendored
Normal file
29636
deps/sqlite/shell.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
255636
deps/sqlite/sqlite3.c
vendored
Normal file
255636
deps/sqlite/sqlite3.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13355
deps/sqlite/sqlite3.h
vendored
Normal file
13355
deps/sqlite/sqlite3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
719
deps/sqlite/sqlite3ext.h
vendored
Normal file
719
deps/sqlite/sqlite3ext.h
vendored
Normal file
@ -0,0 +1,719 @@
|
||||
/*
|
||||
** 2006 June 7
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This header file defines the SQLite interface for use by
|
||||
** shared libraries that want to be imported as extensions into
|
||||
** an SQLite instance. Shared libraries that intend to be loaded
|
||||
** as extensions by SQLite should #include this file instead of
|
||||
** sqlite3.h.
|
||||
*/
|
||||
#ifndef SQLITE3EXT_H
|
||||
#define SQLITE3EXT_H
|
||||
#include "sqlite3.h"
|
||||
|
||||
/*
|
||||
** The following structure holds pointers to all of the SQLite API
|
||||
** routines.
|
||||
**
|
||||
** WARNING: In order to maintain backwards compatibility, add new
|
||||
** interfaces to the end of this structure only. If you insert new
|
||||
** interfaces in the middle of this structure, then older different
|
||||
** versions of SQLite will not be able to load each other's shared
|
||||
** libraries!
|
||||
*/
|
||||
struct sqlite3_api_routines {
|
||||
void * (*aggregate_context)(sqlite3_context*,int nBytes);
|
||||
int (*aggregate_count)(sqlite3_context*);
|
||||
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
|
||||
int (*bind_double)(sqlite3_stmt*,int,double);
|
||||
int (*bind_int)(sqlite3_stmt*,int,int);
|
||||
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
|
||||
int (*bind_null)(sqlite3_stmt*,int);
|
||||
int (*bind_parameter_count)(sqlite3_stmt*);
|
||||
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
|
||||
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
|
||||
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
|
||||
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
|
||||
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
|
||||
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
|
||||
int (*busy_timeout)(sqlite3*,int ms);
|
||||
int (*changes)(sqlite3*);
|
||||
int (*close)(sqlite3*);
|
||||
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
||||
int eTextRep,const char*));
|
||||
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
||||
int eTextRep,const void*));
|
||||
const void * (*column_blob)(sqlite3_stmt*,int iCol);
|
||||
int (*column_bytes)(sqlite3_stmt*,int iCol);
|
||||
int (*column_bytes16)(sqlite3_stmt*,int iCol);
|
||||
int (*column_count)(sqlite3_stmt*pStmt);
|
||||
const char * (*column_database_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_database_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_decltype)(sqlite3_stmt*,int i);
|
||||
const void * (*column_decltype16)(sqlite3_stmt*,int);
|
||||
double (*column_double)(sqlite3_stmt*,int iCol);
|
||||
int (*column_int)(sqlite3_stmt*,int iCol);
|
||||
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
|
||||
const char * (*column_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_origin_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_origin_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_table_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_table_name16)(sqlite3_stmt*,int);
|
||||
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
|
||||
const void * (*column_text16)(sqlite3_stmt*,int iCol);
|
||||
int (*column_type)(sqlite3_stmt*,int iCol);
|
||||
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
|
||||
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
|
||||
int (*complete)(const char*sql);
|
||||
int (*complete16)(const void*sql);
|
||||
int (*create_collation)(sqlite3*,const char*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*));
|
||||
int (*create_collation16)(sqlite3*,const void*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*));
|
||||
int (*create_function)(sqlite3*,const char*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*));
|
||||
int (*create_function16)(sqlite3*,const void*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*));
|
||||
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
|
||||
int (*data_count)(sqlite3_stmt*pStmt);
|
||||
sqlite3 * (*db_handle)(sqlite3_stmt*);
|
||||
int (*declare_vtab)(sqlite3*,const char*);
|
||||
int (*enable_shared_cache)(int);
|
||||
int (*errcode)(sqlite3*db);
|
||||
const char * (*errmsg)(sqlite3*);
|
||||
const void * (*errmsg16)(sqlite3*);
|
||||
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
|
||||
int (*expired)(sqlite3_stmt*);
|
||||
int (*finalize)(sqlite3_stmt*pStmt);
|
||||
void (*free)(void*);
|
||||
void (*free_table)(char**result);
|
||||
int (*get_autocommit)(sqlite3*);
|
||||
void * (*get_auxdata)(sqlite3_context*,int);
|
||||
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
|
||||
int (*global_recover)(void);
|
||||
void (*interruptx)(sqlite3*);
|
||||
sqlite_int64 (*last_insert_rowid)(sqlite3*);
|
||||
const char * (*libversion)(void);
|
||||
int (*libversion_number)(void);
|
||||
void *(*malloc)(int);
|
||||
char * (*mprintf)(const char*,...);
|
||||
int (*open)(const char*,sqlite3**);
|
||||
int (*open16)(const void*,sqlite3**);
|
||||
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
||||
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
||||
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
|
||||
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
|
||||
void *(*realloc)(void*,int);
|
||||
int (*reset)(sqlite3_stmt*pStmt);
|
||||
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_double)(sqlite3_context*,double);
|
||||
void (*result_error)(sqlite3_context*,const char*,int);
|
||||
void (*result_error16)(sqlite3_context*,const void*,int);
|
||||
void (*result_int)(sqlite3_context*,int);
|
||||
void (*result_int64)(sqlite3_context*,sqlite_int64);
|
||||
void (*result_null)(sqlite3_context*);
|
||||
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
|
||||
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_value)(sqlite3_context*,sqlite3_value*);
|
||||
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
|
||||
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
|
||||
const char*,const char*),void*);
|
||||
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
|
||||
char * (*xsnprintf)(int,char*,const char*,...);
|
||||
int (*step)(sqlite3_stmt*);
|
||||
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
|
||||
char const**,char const**,int*,int*,int*);
|
||||
void (*thread_cleanup)(void);
|
||||
int (*total_changes)(sqlite3*);
|
||||
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
|
||||
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
|
||||
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
|
||||
sqlite_int64),void*);
|
||||
void * (*user_data)(sqlite3_context*);
|
||||
const void * (*value_blob)(sqlite3_value*);
|
||||
int (*value_bytes)(sqlite3_value*);
|
||||
int (*value_bytes16)(sqlite3_value*);
|
||||
double (*value_double)(sqlite3_value*);
|
||||
int (*value_int)(sqlite3_value*);
|
||||
sqlite_int64 (*value_int64)(sqlite3_value*);
|
||||
int (*value_numeric_type)(sqlite3_value*);
|
||||
const unsigned char * (*value_text)(sqlite3_value*);
|
||||
const void * (*value_text16)(sqlite3_value*);
|
||||
const void * (*value_text16be)(sqlite3_value*);
|
||||
const void * (*value_text16le)(sqlite3_value*);
|
||||
int (*value_type)(sqlite3_value*);
|
||||
char *(*vmprintf)(const char*,va_list);
|
||||
/* Added ??? */
|
||||
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
|
||||
/* Added by 3.3.13 */
|
||||
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
||||
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
||||
int (*clear_bindings)(sqlite3_stmt*);
|
||||
/* Added by 3.4.1 */
|
||||
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
|
||||
void (*xDestroy)(void *));
|
||||
/* Added by 3.5.0 */
|
||||
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
|
||||
int (*blob_bytes)(sqlite3_blob*);
|
||||
int (*blob_close)(sqlite3_blob*);
|
||||
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
|
||||
int,sqlite3_blob**);
|
||||
int (*blob_read)(sqlite3_blob*,void*,int,int);
|
||||
int (*blob_write)(sqlite3_blob*,const void*,int,int);
|
||||
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*),
|
||||
void(*)(void*));
|
||||
int (*file_control)(sqlite3*,const char*,int,void*);
|
||||
sqlite3_int64 (*memory_highwater)(int);
|
||||
sqlite3_int64 (*memory_used)(void);
|
||||
sqlite3_mutex *(*mutex_alloc)(int);
|
||||
void (*mutex_enter)(sqlite3_mutex*);
|
||||
void (*mutex_free)(sqlite3_mutex*);
|
||||
void (*mutex_leave)(sqlite3_mutex*);
|
||||
int (*mutex_try)(sqlite3_mutex*);
|
||||
int (*open_v2)(const char*,sqlite3**,int,const char*);
|
||||
int (*release_memory)(int);
|
||||
void (*result_error_nomem)(sqlite3_context*);
|
||||
void (*result_error_toobig)(sqlite3_context*);
|
||||
int (*sleep)(int);
|
||||
void (*soft_heap_limit)(int);
|
||||
sqlite3_vfs *(*vfs_find)(const char*);
|
||||
int (*vfs_register)(sqlite3_vfs*,int);
|
||||
int (*vfs_unregister)(sqlite3_vfs*);
|
||||
int (*xthreadsafe)(void);
|
||||
void (*result_zeroblob)(sqlite3_context*,int);
|
||||
void (*result_error_code)(sqlite3_context*,int);
|
||||
int (*test_control)(int, ...);
|
||||
void (*randomness)(int,void*);
|
||||
sqlite3 *(*context_db_handle)(sqlite3_context*);
|
||||
int (*extended_result_codes)(sqlite3*,int);
|
||||
int (*limit)(sqlite3*,int,int);
|
||||
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
|
||||
const char *(*sql)(sqlite3_stmt*);
|
||||
int (*status)(int,int*,int*,int);
|
||||
int (*backup_finish)(sqlite3_backup*);
|
||||
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
|
||||
int (*backup_pagecount)(sqlite3_backup*);
|
||||
int (*backup_remaining)(sqlite3_backup*);
|
||||
int (*backup_step)(sqlite3_backup*,int);
|
||||
const char *(*compileoption_get)(int);
|
||||
int (*compileoption_used)(const char*);
|
||||
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*),
|
||||
void(*xDestroy)(void*));
|
||||
int (*db_config)(sqlite3*,int,...);
|
||||
sqlite3_mutex *(*db_mutex)(sqlite3*);
|
||||
int (*db_status)(sqlite3*,int,int*,int*,int);
|
||||
int (*extended_errcode)(sqlite3*);
|
||||
void (*log)(int,const char*,...);
|
||||
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
|
||||
const char *(*sourceid)(void);
|
||||
int (*stmt_status)(sqlite3_stmt*,int,int);
|
||||
int (*strnicmp)(const char*,const char*,int);
|
||||
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
|
||||
int (*wal_autocheckpoint)(sqlite3*,int);
|
||||
int (*wal_checkpoint)(sqlite3*,const char*);
|
||||
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
|
||||
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
|
||||
int (*vtab_config)(sqlite3*,int op,...);
|
||||
int (*vtab_on_conflict)(sqlite3*);
|
||||
/* Version 3.7.16 and later */
|
||||
int (*close_v2)(sqlite3*);
|
||||
const char *(*db_filename)(sqlite3*,const char*);
|
||||
int (*db_readonly)(sqlite3*,const char*);
|
||||
int (*db_release_memory)(sqlite3*);
|
||||
const char *(*errstr)(int);
|
||||
int (*stmt_busy)(sqlite3_stmt*);
|
||||
int (*stmt_readonly)(sqlite3_stmt*);
|
||||
int (*stricmp)(const char*,const char*);
|
||||
int (*uri_boolean)(const char*,const char*,int);
|
||||
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
|
||||
const char *(*uri_parameter)(const char*,const char*);
|
||||
char *(*xvsnprintf)(int,char*,const char*,va_list);
|
||||
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
|
||||
/* Version 3.8.7 and later */
|
||||
int (*auto_extension)(void(*)(void));
|
||||
int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
|
||||
void(*)(void*));
|
||||
int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
|
||||
void(*)(void*),unsigned char);
|
||||
int (*cancel_auto_extension)(void(*)(void));
|
||||
int (*load_extension)(sqlite3*,const char*,const char*,char**);
|
||||
void *(*malloc64)(sqlite3_uint64);
|
||||
sqlite3_uint64 (*msize)(void*);
|
||||
void *(*realloc64)(void*,sqlite3_uint64);
|
||||
void (*reset_auto_extension)(void);
|
||||
void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
|
||||
void(*)(void*));
|
||||
void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
|
||||
void(*)(void*), unsigned char);
|
||||
int (*strglob)(const char*,const char*);
|
||||
/* Version 3.8.11 and later */
|
||||
sqlite3_value *(*value_dup)(const sqlite3_value*);
|
||||
void (*value_free)(sqlite3_value*);
|
||||
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
|
||||
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
|
||||
/* Version 3.9.0 and later */
|
||||
unsigned int (*value_subtype)(sqlite3_value*);
|
||||
void (*result_subtype)(sqlite3_context*,unsigned int);
|
||||
/* Version 3.10.0 and later */
|
||||
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
|
||||
int (*strlike)(const char*,const char*,unsigned int);
|
||||
int (*db_cacheflush)(sqlite3*);
|
||||
/* Version 3.12.0 and later */
|
||||
int (*system_errno)(sqlite3*);
|
||||
/* Version 3.14.0 and later */
|
||||
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
|
||||
char *(*expanded_sql)(sqlite3_stmt*);
|
||||
/* Version 3.18.0 and later */
|
||||
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
|
||||
/* Version 3.20.0 and later */
|
||||
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
|
||||
sqlite3_stmt**,const char**);
|
||||
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
|
||||
sqlite3_stmt**,const void**);
|
||||
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
|
||||
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
|
||||
void *(*value_pointer)(sqlite3_value*,const char*);
|
||||
int (*vtab_nochange)(sqlite3_context*);
|
||||
int (*value_nochange)(sqlite3_value*);
|
||||
const char *(*vtab_collation)(sqlite3_index_info*,int);
|
||||
/* Version 3.24.0 and later */
|
||||
int (*keyword_count)(void);
|
||||
int (*keyword_name)(int,const char**,int*);
|
||||
int (*keyword_check)(const char*,int);
|
||||
sqlite3_str *(*str_new)(sqlite3*);
|
||||
char *(*str_finish)(sqlite3_str*);
|
||||
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
|
||||
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
|
||||
void (*str_append)(sqlite3_str*, const char *zIn, int N);
|
||||
void (*str_appendall)(sqlite3_str*, const char *zIn);
|
||||
void (*str_appendchar)(sqlite3_str*, int N, char C);
|
||||
void (*str_reset)(sqlite3_str*);
|
||||
int (*str_errcode)(sqlite3_str*);
|
||||
int (*str_length)(sqlite3_str*);
|
||||
char *(*str_value)(sqlite3_str*);
|
||||
/* Version 3.25.0 and later */
|
||||
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*),
|
||||
void (*xValue)(sqlite3_context*),
|
||||
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
|
||||
void(*xDestroy)(void*));
|
||||
/* Version 3.26.0 and later */
|
||||
const char *(*normalized_sql)(sqlite3_stmt*);
|
||||
/* Version 3.28.0 and later */
|
||||
int (*stmt_isexplain)(sqlite3_stmt*);
|
||||
int (*value_frombind)(sqlite3_value*);
|
||||
/* Version 3.30.0 and later */
|
||||
int (*drop_modules)(sqlite3*,const char**);
|
||||
/* Version 3.31.0 and later */
|
||||
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
|
||||
const char *(*uri_key)(const char*,int);
|
||||
const char *(*filename_database)(const char*);
|
||||
const char *(*filename_journal)(const char*);
|
||||
const char *(*filename_wal)(const char*);
|
||||
/* Version 3.32.0 and later */
|
||||
const char *(*create_filename)(const char*,const char*,const char*,
|
||||
int,const char**);
|
||||
void (*free_filename)(const char*);
|
||||
sqlite3_file *(*database_file_object)(const char*);
|
||||
/* Version 3.34.0 and later */
|
||||
int (*txn_state)(sqlite3*,const char*);
|
||||
/* Version 3.36.1 and later */
|
||||
sqlite3_int64 (*changes64)(sqlite3*);
|
||||
sqlite3_int64 (*total_changes64)(sqlite3*);
|
||||
/* Version 3.37.0 and later */
|
||||
int (*autovacuum_pages)(sqlite3*,
|
||||
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
|
||||
void*, void(*)(void*));
|
||||
/* Version 3.38.0 and later */
|
||||
int (*error_offset)(sqlite3*);
|
||||
int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
|
||||
int (*vtab_distinct)(sqlite3_index_info*);
|
||||
int (*vtab_in)(sqlite3_index_info*,int,int);
|
||||
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
|
||||
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
|
||||
/* Version 3.39.0 and later */
|
||||
int (*deserialize)(sqlite3*,const char*,unsigned char*,
|
||||
sqlite3_int64,sqlite3_int64,unsigned);
|
||||
unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
|
||||
unsigned int);
|
||||
const char *(*db_name)(sqlite3*,int);
|
||||
/* Version 3.40.0 and later */
|
||||
int (*value_encoding)(sqlite3_value*);
|
||||
/* Version 3.41.0 and later */
|
||||
int (*is_interrupted)(sqlite3*);
|
||||
/* Version 3.43.0 and later */
|
||||
int (*stmt_explain)(sqlite3_stmt*,int);
|
||||
/* Version 3.44.0 and later */
|
||||
void *(*get_clientdata)(sqlite3*,const char*);
|
||||
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
|
||||
};
|
||||
|
||||
/*
|
||||
** This is the function signature used for all extension entry points. It
|
||||
** is also defined in the file "loadext.c".
|
||||
*/
|
||||
typedef int (*sqlite3_loadext_entry)(
|
||||
sqlite3 *db, /* Handle to the database. */
|
||||
char **pzErrMsg, /* Used to set error string on failure. */
|
||||
const sqlite3_api_routines *pThunk /* Extension API function pointers. */
|
||||
);
|
||||
|
||||
/*
|
||||
** The following macros redefine the API routines so that they are
|
||||
** redirected through the global sqlite3_api structure.
|
||||
**
|
||||
** This header file is also used by the loadext.c source file
|
||||
** (part of the main SQLite library - not an extension) so that
|
||||
** it can get access to the sqlite3_api_routines structure
|
||||
** definition. But the main library does not want to redefine
|
||||
** the API. So the redefinition macros are only valid if the
|
||||
** SQLITE_CORE macros is undefined.
|
||||
*/
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
|
||||
#endif
|
||||
#define sqlite3_bind_blob sqlite3_api->bind_blob
|
||||
#define sqlite3_bind_double sqlite3_api->bind_double
|
||||
#define sqlite3_bind_int sqlite3_api->bind_int
|
||||
#define sqlite3_bind_int64 sqlite3_api->bind_int64
|
||||
#define sqlite3_bind_null sqlite3_api->bind_null
|
||||
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
|
||||
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
|
||||
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
|
||||
#define sqlite3_bind_text sqlite3_api->bind_text
|
||||
#define sqlite3_bind_text16 sqlite3_api->bind_text16
|
||||
#define sqlite3_bind_value sqlite3_api->bind_value
|
||||
#define sqlite3_busy_handler sqlite3_api->busy_handler
|
||||
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
|
||||
#define sqlite3_changes sqlite3_api->changes
|
||||
#define sqlite3_close sqlite3_api->close
|
||||
#define sqlite3_collation_needed sqlite3_api->collation_needed
|
||||
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
|
||||
#define sqlite3_column_blob sqlite3_api->column_blob
|
||||
#define sqlite3_column_bytes sqlite3_api->column_bytes
|
||||
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
|
||||
#define sqlite3_column_count sqlite3_api->column_count
|
||||
#define sqlite3_column_database_name sqlite3_api->column_database_name
|
||||
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
|
||||
#define sqlite3_column_decltype sqlite3_api->column_decltype
|
||||
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
|
||||
#define sqlite3_column_double sqlite3_api->column_double
|
||||
#define sqlite3_column_int sqlite3_api->column_int
|
||||
#define sqlite3_column_int64 sqlite3_api->column_int64
|
||||
#define sqlite3_column_name sqlite3_api->column_name
|
||||
#define sqlite3_column_name16 sqlite3_api->column_name16
|
||||
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
|
||||
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
|
||||
#define sqlite3_column_table_name sqlite3_api->column_table_name
|
||||
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
|
||||
#define sqlite3_column_text sqlite3_api->column_text
|
||||
#define sqlite3_column_text16 sqlite3_api->column_text16
|
||||
#define sqlite3_column_type sqlite3_api->column_type
|
||||
#define sqlite3_column_value sqlite3_api->column_value
|
||||
#define sqlite3_commit_hook sqlite3_api->commit_hook
|
||||
#define sqlite3_complete sqlite3_api->complete
|
||||
#define sqlite3_complete16 sqlite3_api->complete16
|
||||
#define sqlite3_create_collation sqlite3_api->create_collation
|
||||
#define sqlite3_create_collation16 sqlite3_api->create_collation16
|
||||
#define sqlite3_create_function sqlite3_api->create_function
|
||||
#define sqlite3_create_function16 sqlite3_api->create_function16
|
||||
#define sqlite3_create_module sqlite3_api->create_module
|
||||
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
|
||||
#define sqlite3_data_count sqlite3_api->data_count
|
||||
#define sqlite3_db_handle sqlite3_api->db_handle
|
||||
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
|
||||
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
|
||||
#define sqlite3_errcode sqlite3_api->errcode
|
||||
#define sqlite3_errmsg sqlite3_api->errmsg
|
||||
#define sqlite3_errmsg16 sqlite3_api->errmsg16
|
||||
#define sqlite3_exec sqlite3_api->exec
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_expired sqlite3_api->expired
|
||||
#endif
|
||||
#define sqlite3_finalize sqlite3_api->finalize
|
||||
#define sqlite3_free sqlite3_api->free
|
||||
#define sqlite3_free_table sqlite3_api->free_table
|
||||
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
|
||||
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
|
||||
#define sqlite3_get_table sqlite3_api->get_table
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_global_recover sqlite3_api->global_recover
|
||||
#endif
|
||||
#define sqlite3_interrupt sqlite3_api->interruptx
|
||||
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
|
||||
#define sqlite3_libversion sqlite3_api->libversion
|
||||
#define sqlite3_libversion_number sqlite3_api->libversion_number
|
||||
#define sqlite3_malloc sqlite3_api->malloc
|
||||
#define sqlite3_mprintf sqlite3_api->mprintf
|
||||
#define sqlite3_open sqlite3_api->open
|
||||
#define sqlite3_open16 sqlite3_api->open16
|
||||
#define sqlite3_prepare sqlite3_api->prepare
|
||||
#define sqlite3_prepare16 sqlite3_api->prepare16
|
||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
||||
#define sqlite3_profile sqlite3_api->profile
|
||||
#define sqlite3_progress_handler sqlite3_api->progress_handler
|
||||
#define sqlite3_realloc sqlite3_api->realloc
|
||||
#define sqlite3_reset sqlite3_api->reset
|
||||
#define sqlite3_result_blob sqlite3_api->result_blob
|
||||
#define sqlite3_result_double sqlite3_api->result_double
|
||||
#define sqlite3_result_error sqlite3_api->result_error
|
||||
#define sqlite3_result_error16 sqlite3_api->result_error16
|
||||
#define sqlite3_result_int sqlite3_api->result_int
|
||||
#define sqlite3_result_int64 sqlite3_api->result_int64
|
||||
#define sqlite3_result_null sqlite3_api->result_null
|
||||
#define sqlite3_result_text sqlite3_api->result_text
|
||||
#define sqlite3_result_text16 sqlite3_api->result_text16
|
||||
#define sqlite3_result_text16be sqlite3_api->result_text16be
|
||||
#define sqlite3_result_text16le sqlite3_api->result_text16le
|
||||
#define sqlite3_result_value sqlite3_api->result_value
|
||||
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
|
||||
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
|
||||
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
|
||||
#define sqlite3_snprintf sqlite3_api->xsnprintf
|
||||
#define sqlite3_step sqlite3_api->step
|
||||
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
|
||||
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
|
||||
#define sqlite3_total_changes sqlite3_api->total_changes
|
||||
#define sqlite3_trace sqlite3_api->trace
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
|
||||
#endif
|
||||
#define sqlite3_update_hook sqlite3_api->update_hook
|
||||
#define sqlite3_user_data sqlite3_api->user_data
|
||||
#define sqlite3_value_blob sqlite3_api->value_blob
|
||||
#define sqlite3_value_bytes sqlite3_api->value_bytes
|
||||
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
|
||||
#define sqlite3_value_double sqlite3_api->value_double
|
||||
#define sqlite3_value_int sqlite3_api->value_int
|
||||
#define sqlite3_value_int64 sqlite3_api->value_int64
|
||||
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
|
||||
#define sqlite3_value_text sqlite3_api->value_text
|
||||
#define sqlite3_value_text16 sqlite3_api->value_text16
|
||||
#define sqlite3_value_text16be sqlite3_api->value_text16be
|
||||
#define sqlite3_value_text16le sqlite3_api->value_text16le
|
||||
#define sqlite3_value_type sqlite3_api->value_type
|
||||
#define sqlite3_vmprintf sqlite3_api->vmprintf
|
||||
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
|
||||
#define sqlite3_overload_function sqlite3_api->overload_function
|
||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
||||
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
|
||||
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
|
||||
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
|
||||
#define sqlite3_blob_close sqlite3_api->blob_close
|
||||
#define sqlite3_blob_open sqlite3_api->blob_open
|
||||
#define sqlite3_blob_read sqlite3_api->blob_read
|
||||
#define sqlite3_blob_write sqlite3_api->blob_write
|
||||
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
|
||||
#define sqlite3_file_control sqlite3_api->file_control
|
||||
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
|
||||
#define sqlite3_memory_used sqlite3_api->memory_used
|
||||
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
|
||||
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
|
||||
#define sqlite3_mutex_free sqlite3_api->mutex_free
|
||||
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
|
||||
#define sqlite3_mutex_try sqlite3_api->mutex_try
|
||||
#define sqlite3_open_v2 sqlite3_api->open_v2
|
||||
#define sqlite3_release_memory sqlite3_api->release_memory
|
||||
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
|
||||
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
|
||||
#define sqlite3_sleep sqlite3_api->sleep
|
||||
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
|
||||
#define sqlite3_vfs_find sqlite3_api->vfs_find
|
||||
#define sqlite3_vfs_register sqlite3_api->vfs_register
|
||||
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
|
||||
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
|
||||
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
|
||||
#define sqlite3_result_error_code sqlite3_api->result_error_code
|
||||
#define sqlite3_test_control sqlite3_api->test_control
|
||||
#define sqlite3_randomness sqlite3_api->randomness
|
||||
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
|
||||
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
|
||||
#define sqlite3_limit sqlite3_api->limit
|
||||
#define sqlite3_next_stmt sqlite3_api->next_stmt
|
||||
#define sqlite3_sql sqlite3_api->sql
|
||||
#define sqlite3_status sqlite3_api->status
|
||||
#define sqlite3_backup_finish sqlite3_api->backup_finish
|
||||
#define sqlite3_backup_init sqlite3_api->backup_init
|
||||
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
|
||||
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
|
||||
#define sqlite3_backup_step sqlite3_api->backup_step
|
||||
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
|
||||
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
|
||||
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
|
||||
#define sqlite3_db_config sqlite3_api->db_config
|
||||
#define sqlite3_db_mutex sqlite3_api->db_mutex
|
||||
#define sqlite3_db_status sqlite3_api->db_status
|
||||
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
|
||||
#define sqlite3_log sqlite3_api->log
|
||||
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
|
||||
#define sqlite3_sourceid sqlite3_api->sourceid
|
||||
#define sqlite3_stmt_status sqlite3_api->stmt_status
|
||||
#define sqlite3_strnicmp sqlite3_api->strnicmp
|
||||
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
|
||||
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
|
||||
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
|
||||
#define sqlite3_wal_hook sqlite3_api->wal_hook
|
||||
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
|
||||
#define sqlite3_vtab_config sqlite3_api->vtab_config
|
||||
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
|
||||
/* Version 3.7.16 and later */
|
||||
#define sqlite3_close_v2 sqlite3_api->close_v2
|
||||
#define sqlite3_db_filename sqlite3_api->db_filename
|
||||
#define sqlite3_db_readonly sqlite3_api->db_readonly
|
||||
#define sqlite3_db_release_memory sqlite3_api->db_release_memory
|
||||
#define sqlite3_errstr sqlite3_api->errstr
|
||||
#define sqlite3_stmt_busy sqlite3_api->stmt_busy
|
||||
#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
|
||||
#define sqlite3_stricmp sqlite3_api->stricmp
|
||||
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
|
||||
#define sqlite3_uri_int64 sqlite3_api->uri_int64
|
||||
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
|
||||
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
|
||||
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
|
||||
/* Version 3.8.7 and later */
|
||||
#define sqlite3_auto_extension sqlite3_api->auto_extension
|
||||
#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
|
||||
#define sqlite3_bind_text64 sqlite3_api->bind_text64
|
||||
#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
|
||||
#define sqlite3_load_extension sqlite3_api->load_extension
|
||||
#define sqlite3_malloc64 sqlite3_api->malloc64
|
||||
#define sqlite3_msize sqlite3_api->msize
|
||||
#define sqlite3_realloc64 sqlite3_api->realloc64
|
||||
#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
|
||||
#define sqlite3_result_blob64 sqlite3_api->result_blob64
|
||||
#define sqlite3_result_text64 sqlite3_api->result_text64
|
||||
#define sqlite3_strglob sqlite3_api->strglob
|
||||
/* Version 3.8.11 and later */
|
||||
#define sqlite3_value_dup sqlite3_api->value_dup
|
||||
#define sqlite3_value_free sqlite3_api->value_free
|
||||
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
|
||||
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
|
||||
/* Version 3.9.0 and later */
|
||||
#define sqlite3_value_subtype sqlite3_api->value_subtype
|
||||
#define sqlite3_result_subtype sqlite3_api->result_subtype
|
||||
/* Version 3.10.0 and later */
|
||||
#define sqlite3_status64 sqlite3_api->status64
|
||||
#define sqlite3_strlike sqlite3_api->strlike
|
||||
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
|
||||
/* Version 3.12.0 and later */
|
||||
#define sqlite3_system_errno sqlite3_api->system_errno
|
||||
/* Version 3.14.0 and later */
|
||||
#define sqlite3_trace_v2 sqlite3_api->trace_v2
|
||||
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
|
||||
/* Version 3.18.0 and later */
|
||||
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
|
||||
/* Version 3.20.0 and later */
|
||||
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
|
||||
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
|
||||
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
|
||||
#define sqlite3_result_pointer sqlite3_api->result_pointer
|
||||
#define sqlite3_value_pointer sqlite3_api->value_pointer
|
||||
/* Version 3.22.0 and later */
|
||||
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
|
||||
#define sqlite3_value_nochange sqlite3_api->value_nochange
|
||||
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
|
||||
/* Version 3.24.0 and later */
|
||||
#define sqlite3_keyword_count sqlite3_api->keyword_count
|
||||
#define sqlite3_keyword_name sqlite3_api->keyword_name
|
||||
#define sqlite3_keyword_check sqlite3_api->keyword_check
|
||||
#define sqlite3_str_new sqlite3_api->str_new
|
||||
#define sqlite3_str_finish sqlite3_api->str_finish
|
||||
#define sqlite3_str_appendf sqlite3_api->str_appendf
|
||||
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
|
||||
#define sqlite3_str_append sqlite3_api->str_append
|
||||
#define sqlite3_str_appendall sqlite3_api->str_appendall
|
||||
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
|
||||
#define sqlite3_str_reset sqlite3_api->str_reset
|
||||
#define sqlite3_str_errcode sqlite3_api->str_errcode
|
||||
#define sqlite3_str_length sqlite3_api->str_length
|
||||
#define sqlite3_str_value sqlite3_api->str_value
|
||||
/* Version 3.25.0 and later */
|
||||
#define sqlite3_create_window_function sqlite3_api->create_window_function
|
||||
/* Version 3.26.0 and later */
|
||||
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
|
||||
/* Version 3.28.0 and later */
|
||||
#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain
|
||||
#define sqlite3_value_frombind sqlite3_api->value_frombind
|
||||
/* Version 3.30.0 and later */
|
||||
#define sqlite3_drop_modules sqlite3_api->drop_modules
|
||||
/* Version 3.31.0 and later */
|
||||
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
|
||||
#define sqlite3_uri_key sqlite3_api->uri_key
|
||||
#define sqlite3_filename_database sqlite3_api->filename_database
|
||||
#define sqlite3_filename_journal sqlite3_api->filename_journal
|
||||
#define sqlite3_filename_wal sqlite3_api->filename_wal
|
||||
/* Version 3.32.0 and later */
|
||||
#define sqlite3_create_filename sqlite3_api->create_filename
|
||||
#define sqlite3_free_filename sqlite3_api->free_filename
|
||||
#define sqlite3_database_file_object sqlite3_api->database_file_object
|
||||
/* Version 3.34.0 and later */
|
||||
#define sqlite3_txn_state sqlite3_api->txn_state
|
||||
/* Version 3.36.1 and later */
|
||||
#define sqlite3_changes64 sqlite3_api->changes64
|
||||
#define sqlite3_total_changes64 sqlite3_api->total_changes64
|
||||
/* Version 3.37.0 and later */
|
||||
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
|
||||
/* Version 3.38.0 and later */
|
||||
#define sqlite3_error_offset sqlite3_api->error_offset
|
||||
#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value
|
||||
#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct
|
||||
#define sqlite3_vtab_in sqlite3_api->vtab_in
|
||||
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
|
||||
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
|
||||
/* Version 3.39.0 and later */
|
||||
#ifndef SQLITE_OMIT_DESERIALIZE
|
||||
#define sqlite3_deserialize sqlite3_api->deserialize
|
||||
#define sqlite3_serialize sqlite3_api->serialize
|
||||
#endif
|
||||
#define sqlite3_db_name sqlite3_api->db_name
|
||||
/* Version 3.40.0 and later */
|
||||
#define sqlite3_value_encoding sqlite3_api->value_encoding
|
||||
/* Version 3.41.0 and later */
|
||||
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
|
||||
/* Version 3.43.0 and later */
|
||||
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
|
||||
/* Version 3.44.0 and later */
|
||||
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
|
||||
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
|
||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
/* This case when the file really is being compiled as a loadable
|
||||
** extension */
|
||||
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
|
||||
# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
|
||||
# define SQLITE_EXTENSION_INIT3 \
|
||||
extern const sqlite3_api_routines *sqlite3_api;
|
||||
#else
|
||||
/* This case when the file is being statically linked into the
|
||||
** application */
|
||||
# define SQLITE_EXTENSION_INIT1 /*no-op*/
|
||||
# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */
|
||||
# define SQLITE_EXTENSION_INIT3 /*no-op*/
|
||||
#endif
|
||||
|
||||
#endif /* SQLITE3EXT_H */
|
||||
68
example/base64_demo.wren
vendored
Normal file
68
example/base64_demo.wren
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "base64" for Base64
|
||||
|
||||
System.print("=== Base64 Module Demo ===\n")
|
||||
|
||||
System.print("--- Basic Encoding ---")
|
||||
var text = "Hello, World!"
|
||||
var encoded = Base64.encode(text)
|
||||
System.print("Original: %(text)")
|
||||
System.print("Encoded: %(encoded)")
|
||||
|
||||
System.print("\n--- Basic Decoding ---")
|
||||
var decoded = Base64.decode(encoded)
|
||||
System.print("Decoded: %(decoded)")
|
||||
|
||||
System.print("\n--- Various Strings ---")
|
||||
var samples = [
|
||||
"Hello",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"12345",
|
||||
"Special chars: !@#^&*()",
|
||||
"Unicode: cafe resume naive"
|
||||
]
|
||||
|
||||
for (sample in samples) {
|
||||
var enc = Base64.encode(sample)
|
||||
var dec = Base64.decode(enc)
|
||||
System.print("%(sample)")
|
||||
System.print(" -> %(enc)")
|
||||
System.print(" <- %(dec)")
|
||||
System.print("")
|
||||
}
|
||||
|
||||
System.print("--- URL-Safe Encoding ---")
|
||||
var urlData = "Hello+World/Test=End"
|
||||
var urlEncoded = Base64.encodeUrl(urlData)
|
||||
var standardEncoded = Base64.encode(urlData)
|
||||
System.print("Original: %(urlData)")
|
||||
System.print("Standard Base64: %(standardEncoded)")
|
||||
System.print("URL-Safe Base64: %(urlEncoded)")
|
||||
|
||||
System.print("\n--- URL-Safe Decoding ---")
|
||||
var urlDecoded = Base64.decodeUrl(urlEncoded)
|
||||
System.print("Decoded from URL-safe: %(urlDecoded)")
|
||||
|
||||
System.print("\n--- Binary-like Data ---")
|
||||
var binaryText = "Binary: \x00\x01\x02\xFF"
|
||||
var binaryEncoded = Base64.encode(binaryText)
|
||||
System.print("Binary-like string encoded: %(binaryEncoded)")
|
||||
|
||||
System.print("\n--- Empty String ---")
|
||||
System.print("Empty encoded: %(Base64.encode(""))")
|
||||
System.print("Empty decoded: %(Base64.decode(""))")
|
||||
|
||||
System.print("\n--- Practical Example: Basic Auth Header ---")
|
||||
var username = "admin"
|
||||
var password = "secret123"
|
||||
var credentials = "%(username):%(password)"
|
||||
var authHeader = "Basic " + Base64.encode(credentials)
|
||||
System.print("Credentials: %(credentials)")
|
||||
System.print("Authorization header: %(authHeader)")
|
||||
|
||||
System.print("\n--- Practical Example: Data URI ---")
|
||||
var jsonData = "{\"message\":\"Hello\"}"
|
||||
var dataUri = "data:application/json;base64," + Base64.encode(jsonData)
|
||||
System.print("JSON: %(jsonData)")
|
||||
System.print("Data URI: %(dataUri)")
|
||||
89
example/crypto_demo.wren
vendored
Normal file
89
example/crypto_demo.wren
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "crypto" for Crypto, Hash
|
||||
|
||||
System.print("=== Crypto Module Demo ===\n")
|
||||
|
||||
System.print("--- Random Bytes ---")
|
||||
var bytes = Crypto.randomBytes(16)
|
||||
System.print("16 random bytes: %(bytes)")
|
||||
System.print("As hex: %(Hash.toHex(bytes))")
|
||||
|
||||
System.print("\n--- Random Integers ---")
|
||||
System.print("Random integers between 1 and 100:")
|
||||
for (i in 0...5) {
|
||||
var num = Crypto.randomInt(1, 101)
|
||||
System.print(" %(num)")
|
||||
}
|
||||
|
||||
System.print("\n--- Random Integers (different ranges) ---")
|
||||
System.print("1-10: %(Crypto.randomInt(1, 11))")
|
||||
System.print("0-255: %(Crypto.randomInt(0, 256))")
|
||||
System.print("1000-9999: %(Crypto.randomInt(1000, 10000))")
|
||||
|
||||
System.print("\n--- MD5 Hashing ---")
|
||||
var text = "Hello, World!"
|
||||
var md5Bytes = Hash.md5(text)
|
||||
var md5Hex = Hash.toHex(md5Bytes)
|
||||
System.print("Text: %(text)")
|
||||
System.print("MD5: %(md5Hex)")
|
||||
|
||||
System.print("\n--- SHA1 Hashing ---")
|
||||
var sha1Bytes = Hash.sha1(text)
|
||||
var sha1Hex = Hash.toHex(sha1Bytes)
|
||||
System.print("SHA1: %(sha1Hex)")
|
||||
|
||||
System.print("\n--- SHA256 Hashing ---")
|
||||
var sha256Bytes = Hash.sha256(text)
|
||||
var sha256Hex = Hash.toHex(sha256Bytes)
|
||||
System.print("SHA256: %(sha256Hex)")
|
||||
|
||||
System.print("\n--- Hashing Different Inputs ---")
|
||||
var inputs = [
|
||||
"",
|
||||
"test",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"password123"
|
||||
]
|
||||
|
||||
for (input in inputs) {
|
||||
var hash = Hash.toHex(Hash.sha256(input))
|
||||
var displayInput = input
|
||||
if (input == "") displayInput = "(empty string)"
|
||||
if (input.count > 30) displayInput = input[0...30] + "..."
|
||||
System.print("%(displayInput)")
|
||||
System.print(" -> %(hash)")
|
||||
}
|
||||
|
||||
System.print("\n--- Password Hashing Example ---")
|
||||
var password = "mysecretpassword"
|
||||
var passwordHash = Hash.toHex(Hash.sha256(password))
|
||||
System.print("Password: %(password)")
|
||||
System.print("Hash: %(passwordHash)")
|
||||
|
||||
System.print("\n--- Generating Random Token ---")
|
||||
var tokenBytes = Crypto.randomBytes(32)
|
||||
var token = Hash.toHex(tokenBytes)
|
||||
System.print("Random token (64 hex chars): %(token)")
|
||||
|
||||
System.print("\n--- Generating Random Session ID ---")
|
||||
var sessionBytes = Crypto.randomBytes(16)
|
||||
var sessionId = Hash.toHex(sessionBytes)
|
||||
System.print("Session ID: %(sessionId)")
|
||||
|
||||
System.print("\n--- Hash Comparison (constant input) ---")
|
||||
var data = "consistent input"
|
||||
var hash1 = Hash.toHex(Hash.sha256(data))
|
||||
var hash2 = Hash.toHex(Hash.sha256(data))
|
||||
System.print("Same input produces same hash: %(hash1 == hash2)")
|
||||
|
||||
System.print("\n--- Practical Example: File Checksum ---")
|
||||
var fileContent = "This is the content of a file.\nLine 2.\nLine 3."
|
||||
var checksum = Hash.toHex(Hash.sha256(fileContent))
|
||||
System.print("File content checksum (SHA256):")
|
||||
System.print(" %(checksum)")
|
||||
|
||||
System.print("\n--- Practical Example: API Key Generation ---")
|
||||
var apiKeyBytes = Crypto.randomBytes(24)
|
||||
var apiKey = Hash.toHex(apiKeyBytes)
|
||||
System.print("Generated API key: %(apiKey)")
|
||||
86
example/datetime_demo.wren
vendored
Normal file
86
example/datetime_demo.wren
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "datetime" for DateTime, Duration
|
||||
|
||||
System.print("=== DateTime Module Demo ===\n")
|
||||
|
||||
System.print("--- Current Date/Time ---")
|
||||
var now = DateTime.now()
|
||||
System.print("Now: %(now)")
|
||||
System.print("Timestamp: %(now.timestamp)")
|
||||
|
||||
System.print("\n--- Date Components ---")
|
||||
System.print("Year: %(now.year)")
|
||||
System.print("Month: %(now.month)")
|
||||
System.print("Day: %(now.day)")
|
||||
System.print("Hour: %(now.hour)")
|
||||
System.print("Minute: %(now.minute)")
|
||||
System.print("Second: %(now.second)")
|
||||
System.print("Day of Week: %(now.dayOfWeek) (0=Sunday)")
|
||||
System.print("Day of Year: %(now.dayOfYear)")
|
||||
System.print("Is DST: %(now.isDst)")
|
||||
|
||||
System.print("\n--- Creating from Timestamp ---")
|
||||
var epoch = DateTime.fromTimestamp(0)
|
||||
System.print("Unix Epoch: %(epoch)")
|
||||
|
||||
var specificTime = DateTime.fromTimestamp(1704067200)
|
||||
System.print("Jan 1, 2024 00:00 UTC: %(specificTime)")
|
||||
|
||||
System.print("\n--- Formatting with strftime ---")
|
||||
System.print("ISO 8601: %(now.toIso8601)")
|
||||
System.print("Date (Y-m-d): %(now.format("\%Y-\%m-\%d"))")
|
||||
System.print("Time (H:M:S): %(now.format("\%H:\%M:\%S"))")
|
||||
System.print("Full date: %(now.format("\%A, \%B \%d, \%Y"))")
|
||||
System.print("Time with AM/PM: %(now.format("\%I:\%M \%p"))")
|
||||
|
||||
System.print("\n--- Duration Class ---")
|
||||
var oneHour = Duration.fromHours(1)
|
||||
var twoMinutes = Duration.fromMinutes(2)
|
||||
var thirtySeconds = Duration.fromSeconds(30)
|
||||
var oneDay = Duration.fromDays(1)
|
||||
|
||||
System.print("1 hour in seconds: %(oneHour.seconds)")
|
||||
System.print("2 minutes in milliseconds: %(twoMinutes.milliseconds)")
|
||||
System.print("30 seconds in minutes: %(thirtySeconds.minutes)")
|
||||
System.print("1 day in hours: %(oneDay.hours)")
|
||||
|
||||
System.print("\n--- Duration Arithmetic ---")
|
||||
var combined = oneHour + twoMinutes + thirtySeconds
|
||||
System.print("1 hour + 2 minutes + 30 seconds: %(combined)")
|
||||
System.print(" in seconds: %(combined.seconds)")
|
||||
|
||||
var doubled = oneHour * 2
|
||||
System.print("1 hour * 2: %(doubled)")
|
||||
|
||||
var diff = oneHour - twoMinutes
|
||||
System.print("1 hour - 2 minutes: %(diff)")
|
||||
|
||||
System.print("\n--- DateTime Arithmetic ---")
|
||||
var tomorrow = now + Duration.fromDays(1)
|
||||
System.print("Now: %(now)")
|
||||
System.print("Tomorrow: %(tomorrow)")
|
||||
|
||||
var nextHour = now + Duration.fromHours(1)
|
||||
System.print("Next hour: %(nextHour)")
|
||||
|
||||
var yesterday = now - Duration.fromDays(1)
|
||||
System.print("Yesterday: %(yesterday)")
|
||||
|
||||
System.print("\n--- DateTime Comparison ---")
|
||||
System.print("tomorrow > now: %(tomorrow > now)")
|
||||
System.print("yesterday < now: %(yesterday < now)")
|
||||
System.print("now == now: %(now == now)")
|
||||
|
||||
System.print("\n--- Difference Between DateTimes ---")
|
||||
var timeDiff = tomorrow - now
|
||||
System.print("Difference (tomorrow - now): %(timeDiff)")
|
||||
|
||||
System.print("\n--- Practical Example: Event Timing ---")
|
||||
var eventStart = DateTime.now()
|
||||
var eventDuration = Duration.fromHours(2) + Duration.fromMinutes(30)
|
||||
var eventEnd = eventStart + eventDuration
|
||||
|
||||
System.print("Event starts: %(eventStart.format("\%H:\%M"))")
|
||||
System.print("Event duration: %(eventDuration)")
|
||||
System.print("Event ends: %(eventEnd.format("\%H:\%M"))")
|
||||
47
example/dns_demo.wren
vendored
Normal file
47
example/dns_demo.wren
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dns" for Dns
|
||||
|
||||
System.print("=== DNS Module Demo ===\n")
|
||||
|
||||
System.print("--- Basic DNS Lookup ---")
|
||||
var hostname = "example.com"
|
||||
System.print("Looking up: %(hostname)")
|
||||
var addresses = Dns.lookup(hostname)
|
||||
System.print("Addresses: %(addresses)")
|
||||
|
||||
System.print("\n--- IPv4 Only Lookup ---")
|
||||
System.print("Looking up %(hostname) (IPv4 only)")
|
||||
var ipv4Addresses = Dns.lookup(hostname, 4)
|
||||
System.print("IPv4 addresses: %(ipv4Addresses)")
|
||||
|
||||
System.print("\n--- IPv6 Only Lookup ---")
|
||||
System.print("Looking up %(hostname) (IPv6 only)")
|
||||
var ipv6Addresses = Dns.lookup(hostname, 6)
|
||||
System.print("IPv6 addresses: %(ipv6Addresses)")
|
||||
|
||||
System.print("\n--- Multiple Hostname Lookups ---")
|
||||
var hosts = ["google.com", "github.com", "cloudflare.com"]
|
||||
for (host in hosts) {
|
||||
var addrs = Dns.lookup(host, 4)
|
||||
if (addrs.count > 0) {
|
||||
System.print("%(host) -> %(addrs[0])")
|
||||
} else {
|
||||
System.print("%(host) -> (no addresses found)")
|
||||
}
|
||||
}
|
||||
|
||||
System.print("\n--- Localhost Lookup ---")
|
||||
var localhost = Dns.lookup("localhost", 4)
|
||||
System.print("localhost (IPv4): %(localhost)")
|
||||
|
||||
System.print("\n--- Practical Example: Server Connection ---")
|
||||
var serverHost = "httpbin.org"
|
||||
System.print("Resolving server: %(serverHost)")
|
||||
var serverAddrs = Dns.lookup(serverHost, 4)
|
||||
if (serverAddrs.count > 0) {
|
||||
System.print("Server IP: %(serverAddrs[0])")
|
||||
System.print("Ready to connect to %(serverAddrs[0]):80")
|
||||
} else {
|
||||
System.print("Could not resolve %(serverHost)")
|
||||
}
|
||||
51
example/env_demo.wren
vendored
Normal file
51
example/env_demo.wren
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "env" for Environment
|
||||
|
||||
System.print("=== Environment Module Demo ===\n")
|
||||
|
||||
System.print("--- Reading Environment Variables ---")
|
||||
var home = Environment.get("HOME")
|
||||
var path = Environment.get("PATH")
|
||||
var user = Environment.get("USER")
|
||||
|
||||
System.print("HOME: %(home)")
|
||||
System.print("USER: %(user)")
|
||||
System.print("PATH (first 80 chars): %(path[0...80])...")
|
||||
|
||||
System.print("\n--- Reading Non-existent Variable ---")
|
||||
var missing = Environment.get("WREN_NONEXISTENT_VAR_12345")
|
||||
System.print("Missing variable: %(missing)")
|
||||
|
||||
System.print("\n--- Setting Environment Variables ---")
|
||||
Environment.set("WREN_DEMO_VAR", "Hello from Wren!")
|
||||
var demoVar = Environment.get("WREN_DEMO_VAR")
|
||||
System.print("WREN_DEMO_VAR: %(demoVar)")
|
||||
|
||||
Environment.set("WREN_DEMO_NUMBER", "42")
|
||||
System.print("WREN_DEMO_NUMBER: %(Environment.get("WREN_DEMO_NUMBER"))")
|
||||
|
||||
System.print("\n--- Updating Environment Variables ---")
|
||||
Environment.set("WREN_DEMO_VAR", "Updated value")
|
||||
System.print("WREN_DEMO_VAR (updated): %(Environment.get("WREN_DEMO_VAR"))")
|
||||
|
||||
System.print("\n--- Deleting Environment Variables ---")
|
||||
Environment.delete("WREN_DEMO_VAR")
|
||||
var afterDelete = Environment.get("WREN_DEMO_VAR")
|
||||
System.print("WREN_DEMO_VAR (after delete): %(afterDelete)")
|
||||
|
||||
System.print("\n--- Listing All Environment Variables ---")
|
||||
var all = Environment.all
|
||||
System.print("Total environment variables: %(all.count)")
|
||||
|
||||
System.print("\nFirst 10 environment variables:")
|
||||
var count = 0
|
||||
for (entry in all) {
|
||||
if (count >= 10) break
|
||||
var value = entry.value
|
||||
if (value.count > 40) value = value[0...40] + "..."
|
||||
System.print(" %(entry.key) = %(value)")
|
||||
count = count + 1
|
||||
}
|
||||
|
||||
Environment.delete("WREN_DEMO_NUMBER")
|
||||
2
example/http_client.wren
vendored
2
example/http_client.wren
vendored
@ -36,7 +36,9 @@ var host = "127.0.0.1"
|
||||
var port = 8080 // Try to connect to our own http_server example
|
||||
var path = "/"
|
||||
|
||||
while(true) {
|
||||
var result = HttpClient.get(host, port, path)
|
||||
System.print("\n--- Response Received ---
|
||||
")
|
||||
System.print(result)
|
||||
}
|
||||
|
||||
94
example/http_demo.wren
vendored
Normal file
94
example/http_demo.wren
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "http" for Http, HttpResponse, Url
|
||||
|
||||
System.print("=== HTTP Module Demo ===\n")
|
||||
|
||||
System.print("--- URL Parsing ---")
|
||||
var urls = [
|
||||
"http://example.com",
|
||||
"http://example.com:8080",
|
||||
"http://example.com/path/to/resource",
|
||||
"http://example.com/search?q=hello&limit=10"
|
||||
]
|
||||
|
||||
for (urlStr in urls) {
|
||||
var url = Url.parse(urlStr)
|
||||
System.print("URL: %(urlStr)")
|
||||
System.print(" scheme: %(url.scheme), host: %(url.host), port: %(url.port)")
|
||||
System.print(" path: %(url.path), query: %(url.query)")
|
||||
System.print("")
|
||||
}
|
||||
|
||||
System.print("--- Simple GET Request ---")
|
||||
System.print("Fetching http://httpbin.org/get...")
|
||||
var response = Http.get("http://httpbin.org/get")
|
||||
System.print("Status: %(response.statusCode) %(response.statusText)")
|
||||
System.print("OK: %(response.ok)")
|
||||
|
||||
System.print("\n--- Response Headers ---")
|
||||
var count = 0
|
||||
for (header in response.headers) {
|
||||
if (count < 5) System.print(" %(header.key): %(header.value)")
|
||||
count = count + 1
|
||||
}
|
||||
|
||||
System.print("\n--- Response Body (first 300 chars) ---")
|
||||
var body = response.body
|
||||
if (body.count > 300) body = body[0...300] + "..."
|
||||
System.print(body)
|
||||
|
||||
System.print("\n--- Getting Specific Header ---")
|
||||
var contentType = response.header("Content-Type")
|
||||
System.print("Content-Type: %(contentType)")
|
||||
|
||||
System.print("\n--- GET with Query Parameters ---")
|
||||
var searchResponse = Http.get("http://httpbin.org/get?name=wren&version=1.0")
|
||||
System.print("Status: %(searchResponse.statusCode)")
|
||||
System.print("Body contains 'wren': %(searchResponse.body.contains("wren"))")
|
||||
|
||||
System.print("\n--- POST Request ---")
|
||||
System.print("Posting to httpbin.org...")
|
||||
var postResponse = Http.post("http://httpbin.org/post", "Hello from Wren!")
|
||||
System.print("Status: %(postResponse.statusCode)")
|
||||
System.print("Body contains 'Hello': %(postResponse.body.contains("Hello"))")
|
||||
|
||||
System.print("\n--- Custom Headers ---")
|
||||
var headersResponse = Http.get("http://httpbin.org/headers", {
|
||||
"X-Custom-Header": "WrenTest",
|
||||
"Accept": "application/json"
|
||||
})
|
||||
System.print("Status: %(headersResponse.statusCode)")
|
||||
System.print("Body contains 'WrenTest': %(headersResponse.body.contains("WrenTest"))")
|
||||
|
||||
System.print("\n--- Checking Response Status ---")
|
||||
var notFoundResponse = Http.get("http://httpbin.org/status/404")
|
||||
System.print("404 response OK: %(notFoundResponse.ok)")
|
||||
System.print("Status code: %(notFoundResponse.statusCode)")
|
||||
|
||||
var okResponse = Http.get("http://httpbin.org/status/200")
|
||||
System.print("200 response OK: %(okResponse.ok)")
|
||||
|
||||
System.print("\n--- HTTP Response Object ---")
|
||||
System.print(response.toString)
|
||||
|
||||
System.print("\n--- Practical Example: Get UUID ---")
|
||||
var uuidResponse = Http.get("http://httpbin.org/uuid")
|
||||
System.print("Status: %(uuidResponse.statusCode)")
|
||||
var uuidBody = uuidResponse.body
|
||||
if (uuidBody.contains("uuid")) {
|
||||
System.print("UUID response received successfully")
|
||||
System.print("Body: %(uuidBody)")
|
||||
}
|
||||
|
||||
System.print("\n--- HTTPS Request ---")
|
||||
System.print("Fetching https://httpbin.org/get...")
|
||||
var httpsResponse = Http.get("https://httpbin.org/get")
|
||||
System.print("Status: %(httpsResponse.statusCode) %(httpsResponse.statusText)")
|
||||
System.print("OK: %(httpsResponse.ok)")
|
||||
System.print("Body contains 'origin': %(httpsResponse.body.contains("origin"))")
|
||||
|
||||
System.print("\n--- HTTPS POST Request ---")
|
||||
var httpsPost = Http.post("https://httpbin.org/post", {"message": "Hello from Wren over HTTPS!"})
|
||||
System.print("Status: %(httpsPost.statusCode)")
|
||||
System.print("Body contains 'Hello': %(httpsPost.body.contains("Hello"))")
|
||||
161
example/jinja_basics.wren
vendored
Normal file
161
example/jinja_basics.wren
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA BASICS - Variables, Filters, and Expressions")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"variable_simple": "Hello, {{ name }}!",
|
||||
"variable_nested": "User: {{ user.name }}, Age: {{ user.age }}, City: {{ user.address.city }}",
|
||||
"variable_subscript": "First item: {{ items[0] }}, Last item: {{ items[-1] }}",
|
||||
"variable_default": "Name: {{ name|default(\"Anonymous\") }}, Role: {{ role|default(\"Guest\") }}",
|
||||
|
||||
"literal_string": "String: {{ \"hello world\" }}",
|
||||
"literal_number": "Number: {{ 42 }}, Float: {{ 3.14159 }}",
|
||||
"literal_bool": "True: {{ true }}, False: {{ false }}",
|
||||
"literal_list": "List: {{ [1, 2, 3]|join(\", \") }}",
|
||||
|
||||
"expr_math": "Addition: {{ 10 + 5 }}, Subtraction: {{ 10 - 5 }}, Multiplication: {{ 10 * 5 }}, Division: {{ 10 / 4 }}",
|
||||
"expr_modulo": "Modulo: {{ 17 \% 5 }}",
|
||||
"expr_negative": "Negative: {{ -42 }}, Double negative: {{ --42 }}",
|
||||
"expr_parens": "With parens: {{ (2 + 3) * 4 }}, Without: {{ 2 + 3 * 4 }}",
|
||||
"expr_concat": "Concat: {{ first ~ \" \" ~ last }}",
|
||||
|
||||
"expr_compare": "Equal: {{ 5 == 5 }}, Not equal: {{ 5 != 3 }}, Less: {{ 3 < 5 }}, Greater: {{ 5 > 3 }}",
|
||||
"expr_logic": "And: {{ true and false }}, Or: {{ true or false }}, Not: {{ not false }}",
|
||||
"expr_in": "In list: {{ 2 in [1, 2, 3] }}, In string: {{ \"o\" in \"hello\" }}",
|
||||
|
||||
"filter_upper": "{{ text|upper }}",
|
||||
"filter_lower": "{{ text|lower }}",
|
||||
"filter_capitalize": "{{ text|capitalize }}",
|
||||
"filter_title": "{{ text|title }}",
|
||||
"filter_trim": "[{{ text|trim }}]",
|
||||
"filter_length": "Length: {{ items|length }}",
|
||||
"filter_first": "First: {{ items|first }}",
|
||||
"filter_last": "Last: {{ items|last }}",
|
||||
"filter_reverse": "Reversed: {{ items|reverse|join(\", \") }}",
|
||||
"filter_sort": "Sorted: {{ items|sort|join(\", \") }}",
|
||||
"filter_unique": "Unique: {{ items|unique|join(\", \") }}",
|
||||
"filter_join": "Joined: {{ items|join(\" | \") }}",
|
||||
"filter_replace": "{{ text|replace(\"world\", \"universe\") }}",
|
||||
"filter_truncate": "{{ text|truncate(20) }}",
|
||||
"filter_abs": "Absolute: {{ num|abs }}",
|
||||
"filter_round": "Rounded: {{ num|round(2) }}",
|
||||
"filter_int": "Integer: {{ num|int }}",
|
||||
"filter_chain": "{{ text|trim|upper|replace(\"HELLO\", \"HI\") }}",
|
||||
|
||||
"filter_batch": "{\% for row in items|batch(3) \%}[{\% for i in row \%}{{ i }}{\% endfor \%}]{\% endfor \%}",
|
||||
"filter_map": "Names: {{ users|map(\"name\")|join(\", \") }}",
|
||||
"filter_sum": "Sum: {{ numbers|sum }}",
|
||||
"filter_min": "Min: {{ numbers|min }}",
|
||||
"filter_max": "Max: {{ numbers|max }}",
|
||||
|
||||
"filter_striptags": "{{ html|striptags }}",
|
||||
"filter_urlencode": "{{ text|urlencode }}",
|
||||
"filter_wordcount": "Word count: {{ text|wordcount }}",
|
||||
"filter_center": "[{{ text|center(20) }}]",
|
||||
|
||||
"escape_auto": "Auto-escaped: {{ html }}",
|
||||
"escape_safe": "Safe (no escape): {{ html|safe }}",
|
||||
|
||||
"tojson_simple": "{{ data|tojson }}",
|
||||
"tojson_complex": "{{ complex|tojson }}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n--- Simple Variables ---")
|
||||
System.print(env.getTemplate("variable_simple").render({"name": "World"}))
|
||||
System.print(env.getTemplate("variable_nested").render({
|
||||
"user": {
|
||||
"name": "Alice",
|
||||
"age": 28,
|
||||
"address": {"city": "New York", "zip": "10001"}
|
||||
}
|
||||
}))
|
||||
System.print(env.getTemplate("variable_subscript").render({"items": ["apple", "banana", "cherry", "date"]}))
|
||||
System.print(env.getTemplate("variable_default").render({"name": "Bob"}))
|
||||
|
||||
System.print("\n--- Literals ---")
|
||||
System.print(env.getTemplate("literal_string").render({}))
|
||||
System.print(env.getTemplate("literal_number").render({}))
|
||||
System.print(env.getTemplate("literal_bool").render({}))
|
||||
System.print(env.getTemplate("literal_list").render({}))
|
||||
|
||||
System.print("\n--- Mathematical Expressions ---")
|
||||
System.print(env.getTemplate("expr_math").render({}))
|
||||
System.print(env.getTemplate("expr_modulo").render({}))
|
||||
System.print(env.getTemplate("expr_negative").render({}))
|
||||
System.print(env.getTemplate("expr_parens").render({}))
|
||||
System.print(env.getTemplate("expr_concat").render({"first": "John", "last": "Doe"}))
|
||||
|
||||
System.print("\n--- Comparison and Logic ---")
|
||||
System.print(env.getTemplate("expr_compare").render({}))
|
||||
System.print(env.getTemplate("expr_logic").render({}))
|
||||
System.print(env.getTemplate("expr_in").render({}))
|
||||
|
||||
System.print("\n--- String Filters ---")
|
||||
System.print(env.getTemplate("filter_upper").render({"text": "hello world"}))
|
||||
System.print(env.getTemplate("filter_lower").render({"text": "HELLO WORLD"}))
|
||||
System.print(env.getTemplate("filter_capitalize").render({"text": "hello world"}))
|
||||
System.print(env.getTemplate("filter_title").render({"text": "hello world"}))
|
||||
System.print(env.getTemplate("filter_trim").render({"text": " padded string "}))
|
||||
System.print(env.getTemplate("filter_replace").render({"text": "hello world"}))
|
||||
System.print(env.getTemplate("filter_truncate").render({"text": "This is a very long string that should be truncated"}))
|
||||
System.print(env.getTemplate("filter_chain").render({"text": " hello world "}))
|
||||
|
||||
System.print("\n--- List Filters ---")
|
||||
System.print(env.getTemplate("filter_length").render({"items": [1, 2, 3, 4, 5]}))
|
||||
System.print(env.getTemplate("filter_first").render({"items": ["a", "b", "c"]}))
|
||||
System.print(env.getTemplate("filter_last").render({"items": ["a", "b", "c"]}))
|
||||
System.print(env.getTemplate("filter_reverse").render({"items": ["a", "b", "c"]}))
|
||||
System.print(env.getTemplate("filter_sort").render({"items": ["cherry", "apple", "banana"]}))
|
||||
System.print(env.getTemplate("filter_unique").render({"items": ["a", "b", "a", "c", "b", "d"]}))
|
||||
System.print(env.getTemplate("filter_join").render({"items": ["one", "two", "three"]}))
|
||||
System.print(env.getTemplate("filter_batch").render({"items": [1, 2, 3, 4, 5, 6, 7, 8, 9]}))
|
||||
|
||||
System.print("\n--- Numeric Filters ---")
|
||||
System.print(env.getTemplate("filter_abs").render({"num": -42}))
|
||||
System.print(env.getTemplate("filter_round").render({"num": 3.14159}))
|
||||
System.print(env.getTemplate("filter_int").render({"num": 3.99}))
|
||||
System.print(env.getTemplate("filter_sum").render({"numbers": [1, 2, 3, 4, 5]}))
|
||||
System.print(env.getTemplate("filter_min").render({"numbers": [5, 2, 8, 1, 9]}))
|
||||
System.print(env.getTemplate("filter_max").render({"numbers": [5, 2, 8, 1, 9]}))
|
||||
|
||||
System.print("\n--- Object Filters ---")
|
||||
System.print(env.getTemplate("filter_map").render({
|
||||
"users": [
|
||||
{"name": "Alice", "age": 25},
|
||||
{"name": "Bob", "age": 30},
|
||||
{"name": "Charlie", "age": 35}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n--- HTML/Text Filters ---")
|
||||
System.print(env.getTemplate("filter_striptags").render({"html": "<p>Hello <b>World</b></p>"}))
|
||||
System.print(env.getTemplate("filter_urlencode").render({"text": "hello world & more"}))
|
||||
System.print(env.getTemplate("filter_wordcount").render({"text": "The quick brown fox jumps"}))
|
||||
System.print(env.getTemplate("filter_center").render({"text": "Hi"}))
|
||||
|
||||
System.print("\n--- HTML Escaping ---")
|
||||
System.print(env.getTemplate("escape_auto").render({"html": "<script>alert('XSS')</script>"}))
|
||||
System.print(env.getTemplate("escape_safe").render({"html": "<b>Bold text</b>"}))
|
||||
|
||||
System.print("\n--- JSON Output ---")
|
||||
System.print(env.getTemplate("tojson_simple").render({"data": {"name": "Test", "value": 42}}))
|
||||
System.print(env.getTemplate("tojson_complex").render({
|
||||
"complex": {
|
||||
"string": "hello",
|
||||
"number": 123,
|
||||
"bool": true,
|
||||
"null_val": null,
|
||||
"array": [1, 2, 3],
|
||||
"nested": {"a": 1, "b": 2}
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "=" * 60)
|
||||
System.print("End of Basics Demo")
|
||||
System.print("=" * 60)
|
||||
188
example/jinja_control_flow.wren
vendored
Normal file
188
example/jinja_control_flow.wren
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA CONTROL FLOW - Conditionals, Loops, and Tests")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"if_simple": "{\% if show \%}Visible{\% endif \%}",
|
||||
"if_else": "{\% if logged_in \%}Welcome back!{\% else \%}Please log in{\% endif \%}",
|
||||
"if_elif": "{\% if score >= 90 \%}A{\% elif score >= 80 \%}B{\% elif score >= 70 \%}C{\% elif score >= 60 \%}D{\% else \%}F{\% endif \%}",
|
||||
"if_nested": "{\% if user \%}{\% if user.admin \%}Admin: {{ user.name }}{\% else \%}User: {{ user.name }}{\% endif \%}{\% else \%}Guest{\% endif \%}",
|
||||
|
||||
"if_and": "{\% if a and b \%}Both true{\% else \%}At least one false{\% endif \%}",
|
||||
"if_or": "{\% if a or b \%}At least one true{\% else \%}Both false{\% endif \%}",
|
||||
"if_not": "{\% if not hidden \%}Shown{\% else \%}Hidden{\% endif \%}",
|
||||
"if_complex": "{\% if (a > 5 and b < 10) or c == \"special\" \%}Match!{\% else \%}No match{\% endif \%}",
|
||||
|
||||
"if_in_list": "{\% if item in items \%}Found '{{ item }}'{\% else \%}'{{ item }}' not found{\% endif \%}",
|
||||
"if_in_string": "{\% if char in text \%}Contains '{{ char }}'{\% else \%}Does not contain '{{ char }}'{\% endif \%}",
|
||||
"if_not_in": "{\% if item not in items \%}Not in list{\% else \%}In list{\% endif \%}",
|
||||
|
||||
"test_defined": "{\% if var is defined \%}Defined: {{ var }}{\% else \%}Undefined{\% endif \%}",
|
||||
"test_undefined": "{\% if var is undefined \%}Undefined{\% else \%}Defined: {{ var }}{\% endif \%}",
|
||||
"test_none": "{\% if var is none \%}Is none{\% else \%}Not none: {{ var }}{\% endif \%}",
|
||||
"test_even": "{\% if num is even \%}{{ num }} is even{\% else \%}{{ num }} is odd{\% endif \%}",
|
||||
"test_odd": "{\% if num is odd \%}{{ num }} is odd{\% else \%}{{ num }} is even{\% endif \%}",
|
||||
"test_divisible": "{\% if num is divisibleby 3 \%}{{ num }} is divisible by 3{\% else \%}{{ num }} is not divisible by 3{\% endif \%}",
|
||||
"test_sequence": "{\% if items is sequence \%}Is a sequence{\% else \%}Not a sequence{\% endif \%}",
|
||||
"test_string": "{\% if val is string \%}Is a string{\% else \%}Not a string{\% endif \%}",
|
||||
"test_number": "{\% if val is number \%}Is a number{\% else \%}Not a number{\% endif \%}",
|
||||
"test_mapping": "{\% if val is mapping \%}Is a mapping{\% else \%}Not a mapping{\% endif \%}",
|
||||
|
||||
"ternary": "Status: {{ \"Active\" if active else \"Inactive\" }}",
|
||||
"ternary_complex": "{{ user.name if user else \"Anonymous\" }} ({{ \"Admin\" if user and user.admin else \"User\" }})",
|
||||
|
||||
"for_simple": "{\% for item in items \%}{{ item }} {\% endfor \%}",
|
||||
"for_index": "{\% for item in items \%}{{ loop.index }}. {{ item }}\n{\% endfor \%}",
|
||||
"for_index0": "{\% for item in items \%}[{{ loop.index0 }}] {{ item }}\n{\% endfor \%}",
|
||||
"for_first_last": "{\% for item in items \%}{{ \"[\" if loop.first else \"\" }}{{ item }}{{ \"]\" if loop.last else \", \" }}{\% endfor \%}",
|
||||
"for_length": "{\% for item in items \%}Item {{ loop.index }} of {{ loop.length }}: {{ item }}\n{\% endfor \%}",
|
||||
"for_revindex": "{\% for item in items \%}{{ loop.revindex }} remaining: {{ item }}\n{\% endfor \%}",
|
||||
|
||||
"for_else": "{\% for item in items \%}{{ item }}{\% else \%}No items found{\% endfor \%}",
|
||||
"for_nested": "{\% for row in matrix \%}Row {{ loop.index }}: {\% for col in row \%}{{ col }}{\% if not loop.last \%}, {\% endif \%}{\% endfor \%}\n{\% endfor \%}",
|
||||
"for_dict": "{\% for key, value in data \%}{{ key }}: {{ value }}\n{\% endfor \%}",
|
||||
"for_dict_items": "{\% for key in data \%}{{ key }} = {{ data[key] }}\n{\% endfor \%}",
|
||||
|
||||
"for_range": "{\% for i in range(5) \%}{{ i }} {\% endfor \%}",
|
||||
"for_range_start": "{\% for i in range(1, 6) \%}{{ i }} {\% endfor \%}",
|
||||
"for_range_step": "{\% for i in range(0, 10, 2) \%}{{ i }} {\% endfor \%}",
|
||||
|
||||
"for_string": "{\% for char in text \%}{{ char }}-{\% endfor \%}",
|
||||
"for_filtered": "{\% for item in items|sort \%}{{ item }} {\% endfor \%}",
|
||||
"for_reversed": "{\% for item in items|reverse \%}{{ item }} {\% endfor \%}",
|
||||
|
||||
"for_conditional": "{\% for item in items \%}{\% if item > 5 \%}{{ item }} is greater than 5\n{\% endif \%}{\% endfor \%}",
|
||||
|
||||
"set_simple": "{\% set x = 10 \%}x = {{ x }}",
|
||||
"set_expression": "{\% set result = a + b * c \%}Result: {{ result }}",
|
||||
"set_from_filter": "{\% set upper_name = name|upper \%}Upper: {{ upper_name }}",
|
||||
"set_in_loop": "{\% set total = 0 \%}{\% for n in numbers \%}{\% set total = total + n \%}{\% endfor \%}Total: {{ total }}",
|
||||
|
||||
"combined_example": "{\% for user in users \%}{\% if user.active \%}{{ loop.index }}. {{ user.name|title }} ({{ \"Admin\" if user.admin else \"User\" }})\n{\% endif \%}{\% else \%}No active users{\% endfor \%}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n--- Simple Conditionals ---")
|
||||
System.print("show=true: " + env.getTemplate("if_simple").render({"show": true}))
|
||||
System.print("show=false: " + env.getTemplate("if_simple").render({"show": false}))
|
||||
System.print("logged_in=true: " + env.getTemplate("if_else").render({"logged_in": true}))
|
||||
System.print("logged_in=false: " + env.getTemplate("if_else").render({"logged_in": false}))
|
||||
|
||||
System.print("\n--- If-Elif-Else Chain ---")
|
||||
for (score in [95, 85, 75, 65, 50]) {
|
||||
System.print("Score %(score): " + env.getTemplate("if_elif").render({"score": score}))
|
||||
}
|
||||
|
||||
System.print("\n--- Nested Conditionals ---")
|
||||
System.print(env.getTemplate("if_nested").render({"user": {"name": "Alice", "admin": true}}))
|
||||
System.print(env.getTemplate("if_nested").render({"user": {"name": "Bob", "admin": false}}))
|
||||
System.print(env.getTemplate("if_nested").render({"user": null}))
|
||||
|
||||
System.print("\n--- Logical Operators ---")
|
||||
System.print("a=true, b=true: " + env.getTemplate("if_and").render({"a": true, "b": true}))
|
||||
System.print("a=true, b=false: " + env.getTemplate("if_and").render({"a": true, "b": false}))
|
||||
System.print("a=false, b=false: " + env.getTemplate("if_or").render({"a": false, "b": false}))
|
||||
System.print("a=true, b=false: " + env.getTemplate("if_or").render({"a": true, "b": false}))
|
||||
System.print("hidden=false: " + env.getTemplate("if_not").render({"hidden": false}))
|
||||
System.print("hidden=true: " + env.getTemplate("if_not").render({"hidden": true}))
|
||||
|
||||
System.print("\n--- Complex Conditions ---")
|
||||
System.print(env.getTemplate("if_complex").render({"a": 10, "b": 5, "c": "normal"}))
|
||||
System.print(env.getTemplate("if_complex").render({"a": 3, "b": 5, "c": "special"}))
|
||||
System.print(env.getTemplate("if_complex").render({"a": 3, "b": 15, "c": "normal"}))
|
||||
|
||||
System.print("\n--- In Operator ---")
|
||||
System.print(env.getTemplate("if_in_list").render({"item": "banana", "items": ["apple", "banana", "cherry"]}))
|
||||
System.print(env.getTemplate("if_in_list").render({"item": "grape", "items": ["apple", "banana", "cherry"]}))
|
||||
System.print(env.getTemplate("if_in_string").render({"char": "o", "text": "hello"}))
|
||||
System.print(env.getTemplate("if_not_in").render({"item": "x", "items": ["a", "b", "c"]}))
|
||||
|
||||
System.print("\n--- Tests ---")
|
||||
System.print(env.getTemplate("test_defined").render({"var": "Hello"}))
|
||||
System.print(env.getTemplate("test_defined").render({}))
|
||||
System.print(env.getTemplate("test_none").render({"var": null}))
|
||||
System.print(env.getTemplate("test_none").render({"var": "something"}))
|
||||
System.print(env.getTemplate("test_even").render({"num": 4}))
|
||||
System.print(env.getTemplate("test_even").render({"num": 7}))
|
||||
System.print(env.getTemplate("test_divisible").render({"num": 9}))
|
||||
System.print(env.getTemplate("test_divisible").render({"num": 10}))
|
||||
System.print(env.getTemplate("test_sequence").render({"items": [1, 2, 3]}))
|
||||
System.print(env.getTemplate("test_string").render({"val": "hello"}))
|
||||
System.print(env.getTemplate("test_number").render({"val": 42}))
|
||||
System.print(env.getTemplate("test_mapping").render({"val": {"a": 1}}))
|
||||
|
||||
System.print("\n--- Ternary Expressions ---")
|
||||
System.print(env.getTemplate("ternary").render({"active": true}))
|
||||
System.print(env.getTemplate("ternary").render({"active": false}))
|
||||
System.print(env.getTemplate("ternary_complex").render({"user": {"name": "Alice", "admin": true}}))
|
||||
System.print(env.getTemplate("ternary_complex").render({"user": null}))
|
||||
|
||||
System.print("\n--- Basic For Loops ---")
|
||||
System.print(env.getTemplate("for_simple").render({"items": ["a", "b", "c", "d"]}))
|
||||
|
||||
System.print("\n--- Loop Variables ---")
|
||||
System.print("loop.index:")
|
||||
System.print(env.getTemplate("for_index").render({"items": ["Apple", "Banana", "Cherry"]}))
|
||||
System.print("loop.index0:")
|
||||
System.print(env.getTemplate("for_index0").render({"items": ["Apple", "Banana", "Cherry"]}))
|
||||
System.print("loop.first/last:")
|
||||
System.print(env.getTemplate("for_first_last").render({"items": ["Apple", "Banana", "Cherry"]}))
|
||||
System.print("loop.length:")
|
||||
System.print(env.getTemplate("for_length").render({"items": ["A", "B", "C"]}))
|
||||
System.print("loop.revindex:")
|
||||
System.print(env.getTemplate("for_revindex").render({"items": ["A", "B", "C"]}))
|
||||
|
||||
System.print("\n--- For-Else ---")
|
||||
System.print("With items: " + env.getTemplate("for_else").render({"items": ["x", "y", "z"]}))
|
||||
System.print("Empty: " + env.getTemplate("for_else").render({"items": []}))
|
||||
|
||||
System.print("\n--- Nested Loops ---")
|
||||
System.print(env.getTemplate("for_nested").render({
|
||||
"matrix": [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9]
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n--- Dictionary Iteration ---")
|
||||
System.print(env.getTemplate("for_dict").render({"data": {"name": "Alice", "age": 30, "city": "NYC"}}))
|
||||
|
||||
System.print("\n--- Range Function ---")
|
||||
System.print("range(5): " + env.getTemplate("for_range").render({}))
|
||||
System.print("range(1, 6): " + env.getTemplate("for_range_start").render({}))
|
||||
System.print("range(0, 10, 2): " + env.getTemplate("for_range_step").render({}))
|
||||
|
||||
System.print("\n--- Iterating Strings ---")
|
||||
System.print(env.getTemplate("for_string").render({"text": "Hello"}))
|
||||
|
||||
System.print("\n--- Filtered Loops ---")
|
||||
System.print("Sorted: " + env.getTemplate("for_filtered").render({"items": ["cherry", "apple", "banana"]}))
|
||||
System.print("Reversed: " + env.getTemplate("for_reversed").render({"items": [1, 2, 3, 4, 5]}))
|
||||
|
||||
System.print("\n--- Conditionals in Loops ---")
|
||||
System.print(env.getTemplate("for_conditional").render({"items": [3, 7, 2, 9, 4, 8]}))
|
||||
|
||||
System.print("\n--- Set Variable ---")
|
||||
System.print(env.getTemplate("set_simple").render({}))
|
||||
System.print(env.getTemplate("set_expression").render({"a": 2, "b": 3, "c": 4}))
|
||||
System.print(env.getTemplate("set_from_filter").render({"name": "alice"}))
|
||||
|
||||
System.print("\n--- Combined Example ---")
|
||||
System.print(env.getTemplate("combined_example").render({
|
||||
"users": [
|
||||
{"name": "alice smith", "active": true, "admin": true},
|
||||
{"name": "bob jones", "active": false, "admin": false},
|
||||
{"name": "charlie brown", "active": true, "admin": false},
|
||||
{"name": "diana ross", "active": true, "admin": true}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "=" * 60)
|
||||
System.print("End of Control Flow Demo")
|
||||
System.print("=" * 60)
|
||||
107
example/jinja_demo.wren
vendored
Normal file
107
example/jinja_demo.wren
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=== Jinja2-like Templating Demo ===\n")
|
||||
|
||||
var templates = {
|
||||
"base.html": "<!DOCTYPE html>\n<html>\n<head><title>{\% block title \%}Default{\% endblock \%}</title></head>\n<body>\n{\% block content \%}{\% endblock \%}\n</body>\n</html>",
|
||||
|
||||
"page.html": "{\% extends \"base.html\" \%}{\% block title \%}{{ page_title }}{\% endblock \%}{\% block content \%}<h1>{{ heading }}</h1>\n<p>{{ message }}</p>{\% endblock \%}",
|
||||
|
||||
"list.html": "<ul>\n{\% for item in items \%}<li>{{ loop.index }}. {{ item }}</li>\n{\% else \%}<li>No items</li>\n{\% endfor \%}</ul>",
|
||||
|
||||
"user_card.html": "{\% macro user_card(user) \%}<div class=\"card\">\n <h3>{{ user.name|title }}</h3>\n <p>Email: {{ user.email|default(\"N/A\") }}</p>\n <p>Age: {{ user.age|default(\"Unknown\") }}</p>\n</div>{\% endmacro \%}{\% for u in users \%}{{ user_card(u) }}\n{\% endfor \%}",
|
||||
|
||||
"table.html": "<table>\n{\% for row in data|batch(3) \%}<tr>{\% for cell in row \%}<td>{{ cell }}</td>{\% endfor \%}</tr>\n{\% endfor \%}</table>",
|
||||
|
||||
"conditional.html": "{\% if user.role == \"admin\" \%}Admin Panel{\% elif user.role == \"moderator\" \%}Mod Panel{\% else \%}User Panel{\% endif \%}",
|
||||
|
||||
"filters.html": "Original: {{ text }}\nUpper: {{ text|upper }}\nTrimmed: {{ text|trim }}\nLength: {{ text|length }}\nTruncated: {{ text|truncate(10) }}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("--- Template Inheritance ---")
|
||||
var page = env.getTemplate("page.html")
|
||||
System.print(page.render({
|
||||
"page_title": "Welcome",
|
||||
"heading": "Hello, World!",
|
||||
"message": "This demonstrates template inheritance."
|
||||
}))
|
||||
|
||||
System.print("\n--- Loop with Index ---")
|
||||
var list = env.getTemplate("list.html")
|
||||
System.print(list.render({"items": ["Apple", "Banana", "Cherry"]}))
|
||||
|
||||
System.print("\n--- Empty List (else clause) ---")
|
||||
System.print(list.render({"items": []}))
|
||||
|
||||
System.print("\n--- Macros ---")
|
||||
var cards = env.getTemplate("user_card.html")
|
||||
System.print(cards.render({
|
||||
"users": [
|
||||
{"name": "alice smith", "email": "alice@example.com", "age": 28},
|
||||
{"name": "bob jones", "age": 35}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n--- Batch Filter ---")
|
||||
var table = env.getTemplate("table.html")
|
||||
System.print(table.render({"data": [1, 2, 3, 4, 5, 6, 7, 8, 9]}))
|
||||
|
||||
System.print("\n--- Conditionals ---")
|
||||
var cond = env.getTemplate("conditional.html")
|
||||
System.print("Admin: " + cond.render({"user": {"role": "admin"}}))
|
||||
System.print("Mod: " + cond.render({"user": {"role": "moderator"}}))
|
||||
System.print("User: " + cond.render({"user": {"role": "guest"}}))
|
||||
|
||||
System.print("\n--- Filters Demo ---")
|
||||
var filters = env.getTemplate("filters.html")
|
||||
System.print(filters.render({"text": " Hello World! "}))
|
||||
|
||||
System.print("\n--- From String ---")
|
||||
var inline = env.fromString("{\% for n in range(5) \%}{{ n }}{\% if not loop.last \%}, {\% endif \%}{\% endfor \%}")
|
||||
System.print(inline.render({}))
|
||||
|
||||
System.print("\n--- Nested Data Access ---")
|
||||
var nested = env.fromString("{{ users[0].address.city }}")
|
||||
System.print(nested.render({
|
||||
"users": [
|
||||
{"name": "Test", "address": {"city": "New York", "zip": "10001"}}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n--- Expression Tests ---")
|
||||
var tests = env.fromString("{\% if items is sequence \%}Sequence{\% endif \%} {\% if count is even \%}Even{\% endif \%} {\% if name is defined \%}Defined{\% endif \%}")
|
||||
System.print(tests.render({"items": [1, 2, 3], "count": 4, "name": "test"}))
|
||||
|
||||
System.print("\n--- JSON Output ---")
|
||||
var json = env.fromString("{{ data|tojson }}")
|
||||
System.print(json.render({"data": {"name": "Test", "values": [1, 2, 3]}}))
|
||||
|
||||
System.print("\n--- Whitespace Control ---")
|
||||
var ws = env.fromString("Items:{\% for i in items \%}\n {{- i -}}\n{\% endfor \%}")
|
||||
System.print(ws.render({"items": ["a", "b", "c"]}))
|
||||
|
||||
System.print("\n--- Raw Block ---")
|
||||
var raw = env.fromString("{\% raw \%}Use {{ variable }} for output{\% endraw \%}")
|
||||
System.print(raw.render({}))
|
||||
|
||||
System.print("\n--- Set Variable ---")
|
||||
var setVar = env.fromString("{\% set greeting = \"Hello\" \%}{\% set name = \"World\" \%}{{ greeting }}, {{ name }}!")
|
||||
System.print(setVar.render({}))
|
||||
|
||||
System.print("\n--- Comments ---")
|
||||
var comments = env.fromString("Visible{# This is hidden #}Text")
|
||||
System.print(comments.render({}))
|
||||
|
||||
System.print("\n--- Complex Example ---")
|
||||
var complex = env.fromString("{\% for product in products \%}{{ product.name|title }}: ${{ product.price|round(2) }}{\% if product.sale \%} (SALE!){\% endif \%}\n{\% endfor \%}Total: {{ products|map(\"price\")|sum|round(2) }}")
|
||||
System.print(complex.render({
|
||||
"products": [
|
||||
{"name": "widget", "price": 9.99, "sale": true},
|
||||
{"name": "gadget", "price": 24.50, "sale": false},
|
||||
{"name": "gizmo", "price": 15.75, "sale": true}
|
||||
]
|
||||
}))
|
||||
367
example/jinja_inheritance.wren
vendored
Normal file
367
example/jinja_inheritance.wren
vendored
Normal file
@ -0,0 +1,367 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA TEMPLATE INHERITANCE - Extends, Blocks, and Includes")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"base.html": "<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"UTF-8\">
|
||||
<title>{\% block title \%}Default Title{\% endblock \%}</title>
|
||||
<style>
|
||||
{\% block styles \%}
|
||||
body { font-family: sans-serif; margin: 20px; }
|
||||
{\% endblock \%}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{\% block header \%}
|
||||
<h1>My Website</h1>
|
||||
<nav>{\% block nav \%}<a href=\"/\">Home</a>{\% endblock \%}</nav>
|
||||
{\% endblock \%}
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{\% block content \%}
|
||||
<p>Default content goes here.</p>
|
||||
{\% endblock \%}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
{\% block footer \%}
|
||||
<p>Copyright 2024</p>
|
||||
{\% endblock \%}
|
||||
</footer>
|
||||
</body>
|
||||
</html>",
|
||||
|
||||
"page.html": "{\% extends \"base.html\" \%}
|
||||
|
||||
{\% block title \%}{{ page_title }} - My Website{\% endblock \%}
|
||||
|
||||
{\% block content \%}
|
||||
<article>
|
||||
<h2>{{ heading }}</h2>
|
||||
<p>{{ content }}</p>
|
||||
</article>
|
||||
{\% endblock \%}",
|
||||
|
||||
"blog_base.html": "{\% extends \"base.html\" \%}
|
||||
|
||||
{\% block nav \%}
|
||||
{{ super() }}
|
||||
| <a href=\"/blog\">Blog</a>
|
||||
| <a href=\"/about\">About</a>
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block styles \%}
|
||||
{{ super() }}
|
||||
.blog-post { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
|
||||
.blog-meta { color: #666; font-size: 0.9em; }
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block content \%}
|
||||
<section class=\"blog\">
|
||||
{\% block blog_content \%}
|
||||
<p>Blog posts will appear here.</p>
|
||||
{\% endblock \%}
|
||||
</section>
|
||||
{\% endblock \%}",
|
||||
|
||||
"blog_post.html": "{\% extends \"blog_base.html\" \%}
|
||||
|
||||
{\% block title \%}{{ post.title }} - Blog{\% endblock \%}
|
||||
|
||||
{\% block blog_content \%}
|
||||
<article class=\"blog-post\">
|
||||
<h2>{{ post.title }}</h2>
|
||||
<p class=\"blog-meta\">By {{ post.author }} on {{ post.date }}</p>
|
||||
<div class=\"blog-body\">
|
||||
{{ post.body }}
|
||||
</div>
|
||||
{\% if post.tags \%}
|
||||
<p class=\"blog-tags\">Tags: {{ post.tags|join(\", \") }}</p>
|
||||
{\% endif \%}
|
||||
</article>
|
||||
{\% endblock \%}",
|
||||
|
||||
"blog_list.html": "{\% extends \"blog_base.html\" \%}
|
||||
|
||||
{\% block title \%}All Posts - Blog{\% endblock \%}
|
||||
|
||||
{\% block blog_content \%}
|
||||
<h2>Recent Posts</h2>
|
||||
{\% for post in posts \%}
|
||||
<article class=\"blog-post\">
|
||||
<h3>{{ post.title }}</h3>
|
||||
<p class=\"blog-meta\">{{ post.date }} by {{ post.author }}</p>
|
||||
<p>{{ post.excerpt }}</p>
|
||||
<a href=\"/blog/{{ post.slug }}\">Read more...</a>
|
||||
</article>
|
||||
{\% else \%}
|
||||
<p>No posts yet.</p>
|
||||
{\% endfor \%}
|
||||
{\% endblock \%}",
|
||||
|
||||
"admin_base.html": "{\% extends \"base.html\" \%}
|
||||
|
||||
{\% block header \%}
|
||||
<h1>Admin Panel</h1>
|
||||
<nav>
|
||||
<a href=\"/admin\">Dashboard</a> |
|
||||
<a href=\"/admin/users\">Users</a> |
|
||||
<a href=\"/admin/posts\">Posts</a> |
|
||||
<a href=\"/admin/settings\">Settings</a>
|
||||
</nav>
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block styles \%}
|
||||
{{ super() }}
|
||||
.admin-panel { background: #f5f5f5; padding: 20px; }
|
||||
.admin-table { width: 100\%; border-collapse: collapse; }
|
||||
.admin-table th, .admin-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block content \%}
|
||||
<div class=\"admin-panel\">
|
||||
{\% block admin_content \%}
|
||||
<p>Welcome to the admin panel.</p>
|
||||
{\% endblock \%}
|
||||
</div>
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block footer \%}
|
||||
<p>Admin Panel v1.0 | <a href=\"/\">Back to site</a></p>
|
||||
{\% endblock \%}",
|
||||
|
||||
"admin_users.html": "{\% extends \"admin_base.html\" \%}
|
||||
|
||||
{\% block title \%}User Management - Admin{\% endblock \%}
|
||||
|
||||
{\% block admin_content \%}
|
||||
<h2>User Management</h2>
|
||||
<table class=\"admin-table\">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{\% for user in users \%}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.role|title }}</td>
|
||||
<td>{{ \"Active\" if user.active else \"Inactive\" }}</td>
|
||||
</tr>
|
||||
{\% endfor \%}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Total users: {{ users|length }}</p>
|
||||
{\% endblock \%}",
|
||||
|
||||
"sidebar.html": "<aside class=\"sidebar\">
|
||||
<h3>{{ title|default(\"Sidebar\") }}</h3>
|
||||
{\% if items \%}
|
||||
<ul>
|
||||
{\% for item in items \%}
|
||||
<li><a href=\"{{ item.url }}\">{{ item.label }}</a></li>
|
||||
{\% endfor \%}
|
||||
</ul>
|
||||
{\% endif \%}
|
||||
</aside>",
|
||||
|
||||
"alert.html": "<div class=\"alert alert-{{ type|default(\"info\") }}\">
|
||||
{\% if dismissible \%}<button class=\"close\">×</button>{\% endif \%}
|
||||
<strong>{{ title }}:</strong> {{ message }}
|
||||
</div>",
|
||||
|
||||
"page_with_includes.html": "{\% extends \"base.html\" \%}
|
||||
|
||||
{\% block title \%}Page with Includes{\% endblock \%}
|
||||
|
||||
{\% block content \%}
|
||||
<div class=\"container\">
|
||||
<div class=\"main-content\">
|
||||
{\% include \"alert.html\" \%}
|
||||
<h2>Main Content</h2>
|
||||
<p>{{ content }}</p>
|
||||
</div>
|
||||
{\% include \"sidebar.html\" \%}
|
||||
</div>
|
||||
{\% endblock \%}",
|
||||
|
||||
"email_base.html": "From: {{ from_email }}
|
||||
To: {{ to_email }}
|
||||
Subject: {\% block subject \%}No Subject{\% endblock \%}
|
||||
|
||||
{\% block greeting \%}Hello,{\% endblock \%}
|
||||
|
||||
{\% block body \%}
|
||||
This is the email body.
|
||||
{\% endblock \%}
|
||||
|
||||
{\% block signature \%}
|
||||
Best regards,
|
||||
{{ sender_name }}
|
||||
{\% endblock \%}",
|
||||
|
||||
"welcome_email.html": "{\% extends \"email_base.html\" \%}
|
||||
|
||||
{\% block subject \%}Welcome to Our Service, {{ user.name }}!{\% endblock \%}
|
||||
|
||||
{\% block greeting \%}Dear {{ user.name }},{\% endblock \%}
|
||||
|
||||
{\% block body \%}
|
||||
Thank you for signing up for our service!
|
||||
|
||||
Your account details:
|
||||
- Username: {{ user.username }}
|
||||
- Email: {{ user.email }}
|
||||
- Plan: {{ user.plan|title }}
|
||||
|
||||
To get started, please verify your email by clicking the link below:
|
||||
{{ verification_url }}
|
||||
|
||||
If you have any questions, feel free to contact our support team.
|
||||
{\% endblock \%}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("1. BASIC PAGE (extends base.html)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("page.html").render({
|
||||
"page_title": "Welcome",
|
||||
"heading": "Welcome to Our Website",
|
||||
"content": "This is a simple page that extends the base template."
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("2. BLOG POST (three-level inheritance: blog_post -> blog_base -> base)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("blog_post.html").render({
|
||||
"post": {
|
||||
"title": "Getting Started with Jinja Templates",
|
||||
"author": "Alice Smith",
|
||||
"date": "2024-01-15",
|
||||
"body": "Template inheritance is a powerful feature that allows you to build a base skeleton template containing common elements of your site and defines blocks that child templates can override.",
|
||||
"tags": ["jinja", "templates", "tutorial"]
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("3. BLOG LIST (loops within inherited template)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("blog_list.html").render({
|
||||
"posts": [
|
||||
{"title": "First Post", "date": "2024-01-15", "author": "Alice", "excerpt": "This is my first post...", "slug": "first-post"},
|
||||
{"title": "Second Post", "date": "2024-01-14", "author": "Bob", "excerpt": "Another great article...", "slug": "second-post"},
|
||||
{"title": "Third Post", "date": "2024-01-13", "author": "Charlie", "excerpt": "More content here...", "slug": "third-post"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("4. ADMIN USERS PAGE (admin template hierarchy)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("admin_users.html").render({
|
||||
"users": [
|
||||
{"id": 1, "name": "Alice Smith", "email": "alice@example.com", "role": "admin", "active": true},
|
||||
{"id": 2, "name": "Bob Jones", "email": "bob@example.com", "role": "editor", "active": true},
|
||||
{"id": 3, "name": "Charlie Brown", "email": "charlie@example.com", "role": "user", "active": false},
|
||||
{"id": 4, "name": "Diana Ross", "email": "diana@example.com", "role": "user", "active": true}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("5. PAGE WITH INCLUDES")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("page_with_includes.html").render({
|
||||
"type": "warning",
|
||||
"title": "Notice",
|
||||
"message": "This page demonstrates the include feature.",
|
||||
"dismissible": true,
|
||||
"content": "This is the main content of the page.",
|
||||
"title": "Quick Links",
|
||||
"items": [
|
||||
{"label": "Home", "url": "/"},
|
||||
{"label": "About", "url": "/about"},
|
||||
{"label": "Contact", "url": "/contact"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("6. STANDALONE INCLUDE (sidebar)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("sidebar.html").render({
|
||||
"title": "Navigation",
|
||||
"items": [
|
||||
{"label": "Dashboard", "url": "/dashboard"},
|
||||
{"label": "Profile", "url": "/profile"},
|
||||
{"label": "Settings", "url": "/settings"},
|
||||
{"label": "Logout", "url": "/logout"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("7. EMAIL TEMPLATE (text-based inheritance)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("welcome_email.html").render({
|
||||
"from_email": "noreply@example.com",
|
||||
"to_email": "newuser@example.com",
|
||||
"sender_name": "The Example Team",
|
||||
"user": {
|
||||
"name": "John Doe",
|
||||
"username": "johndoe",
|
||||
"email": "newuser@example.com",
|
||||
"plan": "premium"
|
||||
},
|
||||
"verification_url": "https://example.com/verify?token=abc123"
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("8. SUPER() DEMONSTRATION")
|
||||
System.print("-" * 60)
|
||||
|
||||
var superDemo = {
|
||||
"level1": "[L1-START]{\% block content \%}Level1-Content{\% endblock \%}[L1-END]",
|
||||
"level2": "{\% extends \"level1\" \%}{\% block content \%}{{ super() }}+Level2{\% endblock \%}",
|
||||
"level3": "{\% extends \"level2\" \%}{\% block content \%}{{ super() }}+Level3{\% endblock \%}"
|
||||
}
|
||||
|
||||
var superEnv = Environment.new(DictLoader.new(superDemo))
|
||||
System.print("Level 1: " + superEnv.getTemplate("level1").render({}))
|
||||
System.print("Level 2: " + superEnv.getTemplate("level2").render({}))
|
||||
System.print("Level 3: " + superEnv.getTemplate("level3").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("9. MULTIPLE BLOCKS INHERITANCE")
|
||||
System.print("-" * 60)
|
||||
|
||||
var multiBlock = {
|
||||
"base": "A:{\% block a \%}base-a{\% endblock \%} B:{\% block b \%}base-b{\% endblock \%} C:{\% block c \%}base-c{\% endblock \%}",
|
||||
"override_a": "{\% extends \"base\" \%}{\% block a \%}OVERRIDE-A{\% endblock \%}",
|
||||
"override_bc": "{\% extends \"base\" \%}{\% block b \%}OVERRIDE-B{\% endblock \%}{\% block c \%}OVERRIDE-C{\% endblock \%}",
|
||||
"override_all": "{\% extends \"base\" \%}{\% block a \%}NEW-A{\% endblock \%}{\% block b \%}NEW-B{\% endblock \%}{\% block c \%}NEW-C{\% endblock \%}"
|
||||
}
|
||||
|
||||
var multiEnv = Environment.new(DictLoader.new(multiBlock))
|
||||
System.print("Base: " + multiEnv.getTemplate("base").render({}))
|
||||
System.print("Override A: " + multiEnv.getTemplate("override_a").render({}))
|
||||
System.print("Override BC:" + multiEnv.getTemplate("override_bc").render({}))
|
||||
System.print("Override All:" + multiEnv.getTemplate("override_all").render({}))
|
||||
|
||||
System.print("\n" + "=" * 60)
|
||||
System.print("End of Inheritance Demo")
|
||||
System.print("=" * 60)
|
||||
363
example/jinja_macros.wren
vendored
Normal file
363
example/jinja_macros.wren
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA MACROS - Reusable Template Components")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"macro_simple": "{\% macro greet(name) \%}Hello, {{ name }}!{\% endmacro \%}
|
||||
{{ greet(\"World\") }}
|
||||
{{ greet(\"Alice\") }}
|
||||
{{ greet(\"Bob\") }}",
|
||||
|
||||
"macro_default_args": "{\% macro button(text, type=\"primary\", size=\"medium\", disabled=false) \%}
|
||||
<button class=\"btn btn-{{ type }} btn-{{ size }}\"{{ \" disabled\" if disabled else \"\" }}>{{ text }}</button>
|
||||
{\% endmacro \%}
|
||||
{{ button(\"Submit\") }}
|
||||
{{ button(\"Cancel\", \"secondary\") }}
|
||||
{{ button(\"Delete\", \"danger\", \"small\") }}
|
||||
{{ button(\"Disabled\", \"primary\", \"medium\", true) }}",
|
||||
|
||||
"macro_with_content": "{\% macro card(title, footer=\"\") \%}
|
||||
<div class=\"card\">
|
||||
<div class=\"card-header\">{{ title }}</div>
|
||||
<div class=\"card-body\">
|
||||
{\% for item in content \%}{{ item }}{\% endfor \%}
|
||||
</div>
|
||||
{\% if footer \%}<div class=\"card-footer\">{{ footer }}</div>{\% endif \%}
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{{ card(\"My Card\", \"Footer text\") }}",
|
||||
|
||||
"macro_input": "{\% macro input(name, type, value, placeholder, required, label) \%}
|
||||
<div class=\"form-group\">
|
||||
{\% if label \%}<label for=\"{{ name }}\">{{ label }}{\% if required \%} *{\% endif \%}</label>{\% endif \%}
|
||||
<input type=\"{{ type|default(\"text\") }}\" id=\"{{ name }}\" name=\"{{ name }}\" value=\"{{ value }}\" placeholder=\"{{ placeholder }}\"{\% if required \%} required{\% endif \%} class=\"form-control\">
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{{ input(\"username\", \"text\", \"\", \"Enter username\", true, \"Username\") }}
|
||||
{{ input(\"email\", \"email\", \"\", \"\", true, \"Email Address\") }}
|
||||
{{ input(\"password\", \"password\", \"\", \"\", true, \"Password\") }}
|
||||
{{ input(\"bio\", \"text\", \"\", \"Tell us about yourself\", false, \"\") }}",
|
||||
|
||||
"macro_select": "{\% macro select(name, options, selected=\"\", label=\"\", required=false) \%}
|
||||
<div class=\"form-group\">
|
||||
{\% if label \%}<label for=\"{{ name }}\">{{ label }}{\% if required \%} *{\% endif \%}</label>{\% endif \%}
|
||||
<select id=\"{{ name }}\" name=\"{{ name }}\" class=\"form-control\"{\% if required \%} required{\% endif \%}>
|
||||
<option value=\"\">-- Select --</option>
|
||||
{\% for opt in options \%}
|
||||
<option value=\"{{ opt.value }}\"{{ \" selected\" if opt.value == selected else \"\" }}>{{ opt.label }}</option>
|
||||
{\% endfor \%}
|
||||
</select>
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{{ select(\"country\", countries, \"us\", \"Country\", true) }}",
|
||||
|
||||
"macro_table": "{\% macro table(headers, rows, striped=true, bordered=true) \%}
|
||||
<table class=\"table{\% if striped \%} table-striped{\% endif \%}{\% if bordered \%} table-bordered{\% endif \%}\">
|
||||
<thead>
|
||||
<tr>
|
||||
{\% for header in headers \%}
|
||||
<th>{{ header }}</th>
|
||||
{\% endfor \%}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{\% for row in rows \%}
|
||||
<tr>
|
||||
{\% for cell in row \%}
|
||||
<td>{{ cell }}</td>
|
||||
{\% endfor \%}
|
||||
</tr>
|
||||
{\% else \%}
|
||||
<tr><td colspan=\"{{ headers|length }}\">No data available</td></tr>
|
||||
{\% endfor \%}
|
||||
</tbody>
|
||||
</table>
|
||||
{\% endmacro \%}
|
||||
{{ table(headers, data) }}",
|
||||
|
||||
"macro_pagination": "{\% macro pagination(current, total, url_pattern=\"?page=\") \%}
|
||||
<nav class=\"pagination\">
|
||||
{\% if current > 1 \%}
|
||||
<a href=\"{{ url_pattern }}{{ current - 1 }}\" class=\"prev\">« Previous</a>
|
||||
{\% endif \%}
|
||||
|
||||
{\% for page in range(1, total + 1) \%}
|
||||
{\% if page == current \%}
|
||||
<span class=\"current\">{{ page }}</span>
|
||||
{\% else \%}
|
||||
<a href=\"{{ url_pattern }}{{ page }}\">{{ page }}</a>
|
||||
{\% endif \%}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% if current < total \%}
|
||||
<a href=\"{{ url_pattern }}{{ current + 1 }}\" class=\"next\">Next »</a>
|
||||
{\% endif \%}
|
||||
</nav>
|
||||
{\% endmacro \%}
|
||||
{{ pagination(3, 5) }}",
|
||||
|
||||
"macro_breadcrumb": "{\% macro breadcrumb(items) \%}
|
||||
<nav class=\"breadcrumb\">
|
||||
{\% for item in items \%}
|
||||
{\% if loop.last \%}
|
||||
<span class=\"current\">{{ item.label }}</span>
|
||||
{\% else \%}
|
||||
<a href=\"{{ item.url }}\">{{ item.label }}</a>
|
||||
<span class=\"separator\">/</span>
|
||||
{\% endif \%}
|
||||
{\% endfor \%}
|
||||
</nav>
|
||||
{\% endmacro \%}
|
||||
{{ breadcrumb(crumbs) }}",
|
||||
|
||||
"macro_alert": "{\% macro alert(message, type=\"info\", title=\"\", dismissible=true) \%}
|
||||
<div class=\"alert alert-{{ type }}{\% if dismissible \%} alert-dismissible{\% endif \%}\" role=\"alert\">
|
||||
{\% if dismissible \%}<button type=\"button\" class=\"close\">×</button>{\% endif \%}
|
||||
{\% if title \%}<strong>{{ title }}:</strong> {\% endif \%}{{ message }}
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{{ alert(\"Operation completed successfully!\", \"success\", \"Success\") }}
|
||||
{{ alert(\"Please check your input.\", \"warning\", \"Warning\") }}
|
||||
{{ alert(\"An error occurred.\", \"danger\", \"Error\") }}
|
||||
{{ alert(\"Here is some helpful information.\", \"info\") }}",
|
||||
|
||||
"macro_user_card": "{\% macro user_card(user) \%}
|
||||
<div class=\"user-card\">
|
||||
<div class=\"avatar\">
|
||||
<img src=\"{{ user.avatar|default(\"/default-avatar.png\") }}\" alt=\"{{ user.name }}\">
|
||||
</div>
|
||||
<div class=\"info\">
|
||||
<h4>{{ user.name|title }}</h4>
|
||||
<p class=\"role\">{{ user.role|default(\"Member\")|title }}</p>
|
||||
{\% if user.email \%}<p class=\"email\">{{ user.email }}</p>{\% endif \%}
|
||||
{\% if user.bio \%}<p class=\"bio\">{{ user.bio|truncate(100) }}</p>{\% endif \%}
|
||||
<div class=\"stats\">
|
||||
<span>{{ user.posts|default(0) }} posts</span>
|
||||
<span>{{ user.followers|default(0) }} followers</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{\% for user in users \%}
|
||||
{{ user_card(user) }}
|
||||
{\% endfor \%}",
|
||||
|
||||
"macro_nav_menu": "{\% macro nav_menu(items, current_path) \%}
|
||||
<nav class=\"main-nav\">
|
||||
<ul class=\"nav-list\">
|
||||
{\% for item in items \%}
|
||||
<li class=\"nav-item{\% if item.url == current_path \%} active{\% endif \%}{\% if item.children \%} has-children{\% endif \%}\">
|
||||
<a href=\"{{ item.url }}\" class=\"nav-link\">
|
||||
{\% if item.icon \%}<i class=\"icon-{{ item.icon }}\"></i> {\% endif \%}
|
||||
{{ item.label }}
|
||||
</a>
|
||||
{\% if item.children \%}
|
||||
<ul class=\"sub-menu\">
|
||||
{\% for child in item.children \%}
|
||||
<li class=\"nav-item{\% if child.url == current_path \%} active{\% endif \%}\">
|
||||
<a href=\"{{ child.url }}\" class=\"nav-link\">{{ child.label }}</a>
|
||||
</li>
|
||||
{\% endfor \%}
|
||||
</ul>
|
||||
{\% endif \%}
|
||||
</li>
|
||||
{\% endfor \%}
|
||||
</ul>
|
||||
</nav>
|
||||
{\% endmacro \%}
|
||||
{{ nav_menu(menu_items, \"/products\") }}",
|
||||
|
||||
"macro_form": "{\% macro form_start(action, method=\"POST\", id=\"\", classes=\"\") \%}
|
||||
<form action=\"{{ action }}\" method=\"{{ method }}\"{\% if id \%} id=\"{{ id }}\"{\% endif \%}{\% if classes \%} class=\"{{ classes }}\"{\% endif \%}>
|
||||
{\% endmacro \%}
|
||||
|
||||
{\% macro form_end() \%}
|
||||
</form>
|
||||
{\% endmacro \%}
|
||||
|
||||
{\% macro hidden(name, value) \%}
|
||||
<input type=\"hidden\" name=\"{{ name }}\" value=\"{{ value }}\">
|
||||
{\% endmacro \%}
|
||||
|
||||
{\% macro submit(text=\"Submit\", type=\"primary\") \%}
|
||||
<button type=\"submit\" class=\"btn btn-{{ type }}\">{{ text }}</button>
|
||||
{\% endmacro \%}
|
||||
|
||||
{{ form_start(\"/api/contact\", \"POST\", \"contact-form\", \"contact-form\") }}
|
||||
{{ hidden(\"csrf_token\", token) }}
|
||||
<div class=\"form-row\">
|
||||
<label>Name: <input type=\"text\" name=\"name\" required></label>
|
||||
</div>
|
||||
<div class=\"form-row\">
|
||||
<label>Email: <input type=\"email\" name=\"email\" required></label>
|
||||
</div>
|
||||
<div class=\"form-row\">
|
||||
<label>Message: <textarea name=\"message\" rows=\"4\"></textarea></label>
|
||||
</div>
|
||||
{{ submit(\"Send Message\", \"success\") }}
|
||||
{{ form_end() }}",
|
||||
|
||||
"macro_star_rating": "{\% macro stars(rating, max=5) \%}
|
||||
<div class=\"star-rating\" title=\"{{ rating }}/{{ max }}\">
|
||||
{\% for i in range(1, max + 1) \%}
|
||||
{\% if i <= rating \%}
|
||||
<span class=\"star filled\">★</span>
|
||||
{\% else \%}
|
||||
<span class=\"star empty\">☆</span>
|
||||
{\% endif \%}
|
||||
{\% endfor \%}
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
Rating 1: {{ stars(1) }}
|
||||
Rating 3: {{ stars(3) }}
|
||||
Rating 5: {{ stars(5) }}
|
||||
Rating 4/10: {{ stars(4, 10) }}",
|
||||
|
||||
"macro_progress": "{\% macro progress(value, max=100, label=\"\", color=\"primary\") \%}
|
||||
{\% set percentage = (value / max * 100)|round \%}
|
||||
<div class=\"progress-container\">
|
||||
{\% if label \%}<label>{{ label }}: {{ value }}/{{ max }}</label>{\% endif \%}
|
||||
<div class=\"progress\">
|
||||
<div class=\"progress-bar bg-{{ color }}\" style=\"width: {{ percentage }}\%\">
|
||||
{{ percentage }}\%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{\% endmacro \%}
|
||||
{{ progress(25, 100, \"Downloads\") }}
|
||||
{{ progress(75, 100, \"Upload\", \"success\") }}
|
||||
{{ progress(50, 100, \"Processing\", \"warning\") }}
|
||||
{{ progress(3, 10, \"Steps\", \"info\") }}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("1. SIMPLE MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_simple").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("2. MACRO WITH DEFAULT ARGUMENTS")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_default_args").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("3. FORM INPUT MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_input").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("4. SELECT DROPDOWN MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_select").render({
|
||||
"countries": [
|
||||
{"value": "us", "label": "United States"},
|
||||
{"value": "uk", "label": "United Kingdom"},
|
||||
{"value": "ca", "label": "Canada"},
|
||||
{"value": "au", "label": "Australia"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("5. TABLE MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_table").render({
|
||||
"headers": ["ID", "Name", "Email", "Status"],
|
||||
"data": [
|
||||
[1, "Alice", "alice@example.com", "Active"],
|
||||
[2, "Bob", "bob@example.com", "Active"],
|
||||
[3, "Charlie", "charlie@example.com", "Inactive"]
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("6. PAGINATION MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_pagination").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("7. BREADCRUMB MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_breadcrumb").render({
|
||||
"crumbs": [
|
||||
{"label": "Home", "url": "/"},
|
||||
{"label": "Products", "url": "/products"},
|
||||
{"label": "Electronics", "url": "/products/electronics"},
|
||||
{"label": "Laptops", "url": "/products/electronics/laptops"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("8. ALERT MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_alert").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("9. USER CARD MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_user_card").render({
|
||||
"users": [
|
||||
{
|
||||
"name": "alice smith",
|
||||
"role": "admin",
|
||||
"email": "alice@example.com",
|
||||
"avatar": "/avatars/alice.jpg",
|
||||
"bio": "Software engineer with 10 years of experience in web development and cloud architecture.",
|
||||
"posts": 42,
|
||||
"followers": 1250
|
||||
},
|
||||
{
|
||||
"name": "bob jones",
|
||||
"email": "bob@example.com",
|
||||
"posts": 15,
|
||||
"followers": 300
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("10. NAVIGATION MENU MACRO (RECURSIVE)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_nav_menu").render({
|
||||
"menu_items": [
|
||||
{"label": "Home", "url": "/", "icon": "home"},
|
||||
{
|
||||
"label": "Products",
|
||||
"url": "/products",
|
||||
"icon": "box",
|
||||
"children": [
|
||||
{"label": "Electronics", "url": "/products/electronics"},
|
||||
{"label": "Clothing", "url": "/products/clothing"},
|
||||
{"label": "Books", "url": "/products/books"}
|
||||
]
|
||||
},
|
||||
{"label": "About", "url": "/about", "icon": "info"},
|
||||
{"label": "Contact", "url": "/contact", "icon": "envelope"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("11. FORM HELPERS")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_form").render({
|
||||
"token": "abc123xyz789"
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("12. STAR RATING MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_star_rating").render({}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("13. PROGRESS BAR MACRO")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("macro_progress").render({}))
|
||||
|
||||
System.print("\n" + "=" * 60)
|
||||
System.print("End of Macros Demo")
|
||||
System.print("=" * 60)
|
||||
698
example/jinja_real_world.wren
vendored
Normal file
698
example/jinja_real_world.wren
vendored
Normal file
@ -0,0 +1,698 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA REAL-WORLD EXAMPLES")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"invoice.html": "<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Invoice #{{ invoice.number }}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.header { border-bottom: 2px solid #333; padding-bottom: 20px; }
|
||||
.company-info { float: left; }
|
||||
.invoice-info { float: right; text-align: right; }
|
||||
.clear { clear: both; }
|
||||
.addresses { margin: 30px 0; }
|
||||
.from, .to { width: 45\%; display: inline-block; vertical-align: top; }
|
||||
table { width: 100\%; border-collapse: collapse; margin: 20px 0; }
|
||||
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
|
||||
th { background: #f5f5f5; }
|
||||
.amount { text-align: right; }
|
||||
.totals { width: 300px; float: right; }
|
||||
.totals td { border: none; padding: 5px 10px; }
|
||||
.totals .total { font-weight: bold; font-size: 1.2em; border-top: 2px solid #333; }
|
||||
.footer { margin-top: 50px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\"header\">
|
||||
<div class=\"company-info\">
|
||||
<h1>{{ company.name }}</h1>
|
||||
<p>{{ company.address }}<br>
|
||||
{{ company.city }}, {{ company.country }}<br>
|
||||
Tel: {{ company.phone }}<br>
|
||||
Email: {{ company.email }}</p>
|
||||
</div>
|
||||
<div class=\"invoice-info\">
|
||||
<h2>INVOICE</h2>
|
||||
<p><strong>Invoice #:</strong> {{ invoice.number }}<br>
|
||||
<strong>Date:</strong> {{ invoice.date }}<br>
|
||||
<strong>Due Date:</strong> {{ invoice.due_date }}<br>
|
||||
<strong>Status:</strong> {{ invoice.status|upper }}</p>
|
||||
</div>
|
||||
<div class=\"clear\"></div>
|
||||
</div>
|
||||
|
||||
<div class=\"addresses\">
|
||||
<div class=\"from\">
|
||||
<h3>From:</h3>
|
||||
<p>{{ company.name }}<br>
|
||||
{{ company.address }}<br>
|
||||
{{ company.city }}, {{ company.country }}</p>
|
||||
</div>
|
||||
<div class=\"to\">
|
||||
<h3>Bill To:</h3>
|
||||
<p><strong>{{ client.name }}</strong><br>
|
||||
{{ client.address }}<br>
|
||||
{{ client.city }}, {{ client.country }}<br>
|
||||
{\% if client.vat \%}VAT: {{ client.vat }}{\% endif \%}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th class=\"amount\">Unit Price</th>
|
||||
<th class=\"amount\">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{\% for item in invoice.items \%}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ item.description }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td class=\"amount\">${{ item.price|round(2) }}</td>
|
||||
<td class=\"amount\">${{ (item.quantity * item.price)|round(2) }}</td>
|
||||
</tr>
|
||||
{\% endfor \%}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class=\"totals\">
|
||||
<tr>
|
||||
<td>Subtotal:</td>
|
||||
<td class=\"amount\">${{ invoice.subtotal|round(2) }}</td>
|
||||
</tr>
|
||||
{\% if invoice.discount \%}
|
||||
<tr>
|
||||
<td>Discount ({{ invoice.discount_percent }}\%):</td>
|
||||
<td class=\"amount\">-${{ invoice.discount|round(2) }}</td>
|
||||
</tr>
|
||||
{\% endif \%}
|
||||
<tr>
|
||||
<td>Tax ({{ invoice.tax_rate }}\%):</td>
|
||||
<td class=\"amount\">${{ invoice.tax|round(2) }}</td>
|
||||
</tr>
|
||||
<tr class=\"total\">
|
||||
<td>Total:</td>
|
||||
<td class=\"amount\">${{ invoice.total|round(2) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class=\"clear\"></div>
|
||||
|
||||
{\% if invoice.notes \%}
|
||||
<div class=\"notes\">
|
||||
<h3>Notes:</h3>
|
||||
<p>{{ invoice.notes }}</p>
|
||||
</div>
|
||||
{\% endif \%}
|
||||
|
||||
<div class=\"footer\">
|
||||
<p>Payment Terms: {{ invoice.payment_terms|default(\"Net 30\") }}</p>
|
||||
<p>Please make payment to: {{ company.bank_account }}</p>
|
||||
<p>Thank you for your business!</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>",
|
||||
|
||||
"report.md": "# {{ report.title }}
|
||||
|
||||
**Generated:** {{ report.date }}
|
||||
**Author:** {{ report.author }}
|
||||
**Period:** {{ report.period_start }} to {{ report.period_end }}
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
{{ report.summary }}
|
||||
|
||||
## Key Metrics
|
||||
|
||||
| Metric | Value | Change |
|
||||
|--------|-------|--------|
|
||||
{\% for metric in report.metrics \%}
|
||||
| {{ metric.name }} | {{ metric.value }} | {{ \"+\" if metric.change > 0 else \"\" }}{{ metric.change }}\% |
|
||||
{\% endfor \%}
|
||||
|
||||
## Performance by Category
|
||||
|
||||
{\% for category in report.categories \%}
|
||||
### {{ category.name }}
|
||||
|
||||
- **Revenue:** ${{ category.revenue|round(2) }}
|
||||
- **Units Sold:** {{ category.units }}
|
||||
- **Growth:** {{ category.growth }}\%
|
||||
|
||||
{\% if category.top_products \%}
|
||||
Top products:
|
||||
{\% for product in category.top_products \%}
|
||||
{{ loop.index }}. {{ product.name }} (${{ product.revenue|round(2) }})
|
||||
{\% endfor \%}
|
||||
{\% endif \%}
|
||||
|
||||
{\% endfor \%}
|
||||
|
||||
## Recommendations
|
||||
|
||||
{\% for rec in report.recommendations \%}
|
||||
{{ loop.index }}. **{{ rec.priority|upper }}**: {{ rec.text }}
|
||||
{\% endfor \%}
|
||||
|
||||
---
|
||||
|
||||
*This report was automatically generated.*",
|
||||
|
||||
"api_response.json": "{
|
||||
\"status\": \"{{ response.status }}\",
|
||||
\"code\": {{ response.code }},
|
||||
\"message\": \"{{ response.message }}\",
|
||||
\"timestamp\": \"{{ response.timestamp }}\",
|
||||
\"data\": {
|
||||
{\% if response.data.user \%}
|
||||
\"user\": {
|
||||
\"id\": {{ response.data.user.id }},
|
||||
\"name\": \"{{ response.data.user.name }}\",
|
||||
\"email\": \"{{ response.data.user.email }}\",
|
||||
\"role\": \"{{ response.data.user.role }}\",
|
||||
\"created_at\": \"{{ response.data.user.created_at }}\"
|
||||
},
|
||||
{\% endif \%}
|
||||
{\% if response.data.items \%}
|
||||
\"items\": [
|
||||
{\% for item in response.data.items \%}
|
||||
{
|
||||
\"id\": {{ item.id }},
|
||||
\"name\": \"{{ item.name }}\",
|
||||
\"price\": {{ item.price }},
|
||||
\"available\": {{ \"true\" if item.available else \"false\" }}
|
||||
}{{ \",\" if not loop.last else \"\" }}
|
||||
{\% endfor \%}
|
||||
],
|
||||
\"total\": {{ response.data.items|length }},
|
||||
{\% endif \%}
|
||||
\"pagination\": {
|
||||
\"page\": {{ response.pagination.page }},
|
||||
\"per_page\": {{ response.pagination.per_page }},
|
||||
\"total_pages\": {{ response.pagination.total_pages }},
|
||||
\"total_items\": {{ response.pagination.total_items }}
|
||||
}
|
||||
}
|
||||
}",
|
||||
|
||||
"config.yaml": "# Application Configuration
|
||||
# Generated: {{ timestamp }}
|
||||
|
||||
app:
|
||||
name: {{ config.app.name }}
|
||||
version: {{ config.app.version }}
|
||||
environment: {{ config.app.environment }}
|
||||
debug: {{ \"true\" if config.app.debug else \"false\" }}
|
||||
|
||||
server:
|
||||
host: {{ config.server.host }}
|
||||
port: {{ config.server.port }}
|
||||
workers: {{ config.server.workers }}
|
||||
timeout: {{ config.server.timeout }}
|
||||
|
||||
database:
|
||||
driver: {{ config.database.driver }}
|
||||
host: {{ config.database.host }}
|
||||
port: {{ config.database.port }}
|
||||
name: {{ config.database.name }}
|
||||
user: {{ config.database.user }}
|
||||
pool_size: {{ config.database.pool_size }}
|
||||
|
||||
cache:
|
||||
driver: {{ config.cache.driver }}
|
||||
host: {{ config.cache.host }}
|
||||
port: {{ config.cache.port }}
|
||||
ttl: {{ config.cache.ttl }}
|
||||
|
||||
logging:
|
||||
level: {{ config.logging.level }}
|
||||
format: \"{{ config.logging.format }}\"
|
||||
handlers:
|
||||
{\% for handler in config.logging.handlers \%}
|
||||
- type: {{ handler.type }}
|
||||
{\% if handler.path \%}path: {{ handler.path }}{\% endif \%}
|
||||
{\% if handler.max_size \%}max_size: {{ handler.max_size }}{\% endif \%}
|
||||
{\% endfor \%}
|
||||
|
||||
features:
|
||||
{\% for feature, enabled in config.features \%}
|
||||
{{ feature }}: {{ \"true\" if enabled else \"false\" }}
|
||||
{\% endfor \%}",
|
||||
|
||||
"sitemap.xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">
|
||||
{\% for page in pages \%}
|
||||
<url>
|
||||
<loc>{{ base_url }}{{ page.url }}</loc>
|
||||
<lastmod>{{ page.last_modified }}</lastmod>
|
||||
<changefreq>{{ page.change_freq|default(\"weekly\") }}</changefreq>
|
||||
<priority>{{ page.priority|default(\"0.5\") }}</priority>
|
||||
</url>
|
||||
{\% endfor \%}
|
||||
</urlset>",
|
||||
|
||||
"dockerfile": "# Dockerfile for {{ app.name }}
|
||||
# Generated automatically - do not edit manually
|
||||
|
||||
FROM {{ app.base_image }}
|
||||
|
||||
LABEL maintainer=\"{{ app.maintainer }}\"
|
||||
LABEL version=\"{{ app.version }}\"
|
||||
|
||||
# Set environment variables
|
||||
{\% for key, value in app.env_vars \%}
|
||||
ENV {{ key }}={{ value }}
|
||||
{\% endfor \%}
|
||||
|
||||
# Set working directory
|
||||
WORKDIR {{ app.workdir }}
|
||||
|
||||
# Install system dependencies
|
||||
{\% if app.system_packages \%}
|
||||
RUN apt-get update && apt-get install -y \\
|
||||
{\% for pkg in app.system_packages \%}
|
||||
{{ pkg }}{\% if not loop.last \%} \\{\% endif \%}
|
||||
{\% endfor \%}
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
{\% endif \%}
|
||||
|
||||
# Copy dependency files
|
||||
{\% for file in app.dependency_files \%}
|
||||
COPY {{ file }} .
|
||||
{\% endfor \%}
|
||||
|
||||
# Install application dependencies
|
||||
{\% if app.install_cmd \%}
|
||||
RUN {{ app.install_cmd }}
|
||||
{\% endif \%}
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Expose port
|
||||
EXPOSE {{ app.port }}
|
||||
|
||||
# Health check
|
||||
{\% if app.healthcheck \%}
|
||||
HEALTHCHECK --interval={{ app.healthcheck.interval }} --timeout={{ app.healthcheck.timeout }} \\
|
||||
CMD {{ app.healthcheck.cmd }}
|
||||
{\% endif \%}
|
||||
|
||||
# Run the application
|
||||
CMD [\"{{ app.cmd }}\"]",
|
||||
|
||||
"readme.md": "# {{ project.name }}
|
||||
|
||||
{{ project.description }}
|
||||
|
||||
## Features
|
||||
|
||||
{\% for feature in project.features \%}
|
||||
- {{ feature }}
|
||||
{\% endfor \%}
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
{{ project.install_cmd }}
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```{{ project.code_lang }}
|
||||
{{ project.quick_start }}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
{\% for opt in project.config_options \%}
|
||||
| `{{ opt.name }}` | {{ opt.type }} | {{ opt.default|default(\"N/A\") }} | {{ opt.description }} |
|
||||
{\% endfor \%}
|
||||
|
||||
## API Reference
|
||||
|
||||
{\% for endpoint in project.api_endpoints \%}
|
||||
### {{ endpoint.method }} {{ endpoint.path }}
|
||||
|
||||
{{ endpoint.description }}
|
||||
|
||||
**Parameters:**
|
||||
{\% for param in endpoint.params \%}
|
||||
- `{{ param.name }}` ({{ param.type }}{\% if param.required \%}, required{\% endif \%}): {{ param.description }}
|
||||
{\% endfor \%}
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{{ endpoint.response_example }}
|
||||
```
|
||||
|
||||
{\% endfor \%}
|
||||
|
||||
## License
|
||||
|
||||
{{ project.license }}
|
||||
|
||||
## Author
|
||||
|
||||
{{ project.author }} - {{ project.author_email }}",
|
||||
|
||||
"changelog.md": "# Changelog
|
||||
|
||||
All notable changes to {{ project_name }} will be documented in this file.
|
||||
|
||||
{\% for release in releases \%}
|
||||
## [{{ release.version }}] - {{ release.date }}
|
||||
|
||||
{\% if release.breaking \%}
|
||||
### Breaking Changes
|
||||
{\% for item in release.breaking \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.added \%}
|
||||
### Added
|
||||
{\% for item in release.added \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.changed \%}
|
||||
### Changed
|
||||
{\% for item in release.changed \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.deprecated \%}
|
||||
### Deprecated
|
||||
{\% for item in release.deprecated \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.removed \%}
|
||||
### Removed
|
||||
{\% for item in release.removed \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.fixed \%}
|
||||
### Fixed
|
||||
{\% for item in release.fixed \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% if release.security \%}
|
||||
### Security
|
||||
{\% for item in release.security \%}
|
||||
- {{ item }}
|
||||
{\% endfor \%}
|
||||
|
||||
{\% endif \%}
|
||||
{\% endfor \%}"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("1. INVOICE (HTML)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("invoice.html").render({
|
||||
"company": {
|
||||
"name": "Acme Corporation",
|
||||
"address": "123 Business Street",
|
||||
"city": "New York, NY 10001",
|
||||
"country": "USA",
|
||||
"phone": "+1 (555) 123-4567",
|
||||
"email": "billing@acme.com",
|
||||
"bank_account": "Bank of America - 1234567890"
|
||||
},
|
||||
"client": {
|
||||
"name": "TechStart Inc.",
|
||||
"address": "456 Innovation Ave",
|
||||
"city": "San Francisco, CA 94102",
|
||||
"country": "USA",
|
||||
"vat": "US123456789"
|
||||
},
|
||||
"invoice": {
|
||||
"number": "INV-2024-001",
|
||||
"date": "2024-01-15",
|
||||
"due_date": "2024-02-14",
|
||||
"status": "pending",
|
||||
"items": [
|
||||
{"description": "Web Development Services", "quantity": 40, "price": 150},
|
||||
{"description": "UI/UX Design", "quantity": 20, "price": 125},
|
||||
{"description": "Server Setup & Configuration", "quantity": 8, "price": 200},
|
||||
{"description": "Monthly Maintenance (Jan)", "quantity": 1, "price": 500}
|
||||
],
|
||||
"subtotal": 9600,
|
||||
"discount": 480,
|
||||
"discount_percent": 5,
|
||||
"tax_rate": 8.5,
|
||||
"tax": 775.2,
|
||||
"total": 9895.2,
|
||||
"notes": "Thank you for choosing Acme Corporation. Payment is due within 30 days.",
|
||||
"payment_terms": "Net 30"
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("2. BUSINESS REPORT (Markdown)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("report.md").render({
|
||||
"report": {
|
||||
"title": "Q4 2024 Sales Report",
|
||||
"date": "2024-01-05",
|
||||
"author": "Analytics Team",
|
||||
"period_start": "2024-10-01",
|
||||
"period_end": "2024-12-31",
|
||||
"summary": "Q4 2024 showed strong performance with overall revenue growth of 15\% compared to Q3. The Electronics category led growth while we saw moderate decline in Accessories.",
|
||||
"metrics": [
|
||||
{"name": "Total Revenue", "value": "$1.2M", "change": 15},
|
||||
{"name": "Orders", "value": "8,450", "change": 12},
|
||||
{"name": "Avg Order Value", "value": "$142", "change": 3},
|
||||
{"name": "Customer Retention", "value": "78\%", "change": -2}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Electronics",
|
||||
"revenue": 650000,
|
||||
"units": 4200,
|
||||
"growth": 22,
|
||||
"top_products": [
|
||||
{"name": "Laptop Pro X", "revenue": 180000},
|
||||
{"name": "Wireless Headphones", "revenue": 95000},
|
||||
{"name": "Smart Watch V3", "revenue": 72000}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Clothing",
|
||||
"revenue": 380000,
|
||||
"units": 12500,
|
||||
"growth": 8,
|
||||
"top_products": [
|
||||
{"name": "Winter Jacket", "revenue": 65000},
|
||||
{"name": "Running Shoes", "revenue": 48000}
|
||||
]
|
||||
}
|
||||
],
|
||||
"recommendations": [
|
||||
{"priority": "high", "text": "Increase inventory for Electronics category to meet Q1 demand"},
|
||||
{"priority": "medium", "text": "Launch targeted campaign for Accessories to reverse decline"},
|
||||
{"priority": "low", "text": "Consider expanding Clothing line with spring collection"}
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("3. API RESPONSE (JSON)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("api_response.json").render({
|
||||
"response": {
|
||||
"status": "success",
|
||||
"code": 200,
|
||||
"message": "Data retrieved successfully",
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 12345,
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"role": "admin",
|
||||
"created_at": "2023-06-15T08:00:00Z"
|
||||
},
|
||||
"items": [
|
||||
{"id": 1, "name": "Widget A", "price": 29.99, "available": true},
|
||||
{"id": 2, "name": "Widget B", "price": 49.99, "available": true},
|
||||
{"id": 3, "name": "Widget C", "price": 19.99, "available": false}
|
||||
]
|
||||
},
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"per_page": 10,
|
||||
"total_pages": 5,
|
||||
"total_items": 47
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("4. CONFIG FILE (YAML)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("config.yaml").render({
|
||||
"timestamp": "2024-01-15 10:30:00",
|
||||
"config": {
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"version": "2.1.0",
|
||||
"environment": "production",
|
||||
"debug": false
|
||||
},
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8080,
|
||||
"workers": 4,
|
||||
"timeout": 30
|
||||
},
|
||||
"database": {
|
||||
"driver": "postgresql",
|
||||
"host": "db.example.com",
|
||||
"port": 5432,
|
||||
"name": "myapp_prod",
|
||||
"user": "app_user",
|
||||
"pool_size": 20
|
||||
},
|
||||
"cache": {
|
||||
"driver": "redis",
|
||||
"host": "cache.example.com",
|
||||
"port": 6379,
|
||||
"ttl": 3600
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"format": "\%(asctime)s - \%(name)s - \%(levelname)s - \%(message)s",
|
||||
"handlers": [
|
||||
{"type": "console"},
|
||||
{"type": "file", "path": "/var/log/myapp.log", "max_size": "100MB"}
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"new_dashboard": true,
|
||||
"beta_api": false,
|
||||
"analytics": true
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("5. SITEMAP (XML)")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("sitemap.xml").render({
|
||||
"base_url": "https://example.com",
|
||||
"pages": [
|
||||
{"url": "/", "last_modified": "2024-01-15", "change_freq": "daily", "priority": "1.0"},
|
||||
{"url": "/about", "last_modified": "2024-01-10", "change_freq": "monthly", "priority": "0.8"},
|
||||
{"url": "/products", "last_modified": "2024-01-14", "change_freq": "weekly", "priority": "0.9"},
|
||||
{"url": "/blog", "last_modified": "2024-01-15", "change_freq": "daily", "priority": "0.7"},
|
||||
{"url": "/contact", "last_modified": "2024-01-01", "change_freq": "yearly", "priority": "0.5"}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("6. DOCKERFILE")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("dockerfile").render({
|
||||
"app": {
|
||||
"name": "my-python-app",
|
||||
"base_image": "python:3.11-slim",
|
||||
"maintainer": "dev@example.com",
|
||||
"version": "1.0.0",
|
||||
"workdir": "/app",
|
||||
"port": 8000,
|
||||
"env_vars": {
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
"PYTHONDONTWRITEBYTECODE": "1",
|
||||
"APP_ENV": "production"
|
||||
},
|
||||
"system_packages": ["gcc", "libpq-dev", "curl"],
|
||||
"dependency_files": ["requirements.txt", "setup.py"],
|
||||
"install_cmd": "pip install --no-cache-dir -r requirements.txt",
|
||||
"healthcheck": {
|
||||
"interval": "30s",
|
||||
"timeout": "10s",
|
||||
"cmd": "curl -f http://localhost:8000/health || exit 1"
|
||||
},
|
||||
"cmd": "gunicorn -w 4 -b 0.0.0.0:8000 app:app"
|
||||
}
|
||||
}))
|
||||
|
||||
System.print("\n" + "-" * 60)
|
||||
System.print("7. CHANGELOG")
|
||||
System.print("-" * 60)
|
||||
System.print(env.getTemplate("changelog.md").render({
|
||||
"project_name": "MyAwesomeLib",
|
||||
"releases": [
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"date": "2024-01-15",
|
||||
"breaking": [
|
||||
"Removed deprecated `oldMethod()` function",
|
||||
"Changed default timeout from 30s to 60s"
|
||||
],
|
||||
"added": [
|
||||
"New async API support",
|
||||
"Plugin system for extensibility",
|
||||
"TypeScript type definitions"
|
||||
],
|
||||
"changed": [
|
||||
"Improved error messages",
|
||||
"Updated dependencies to latest versions"
|
||||
],
|
||||
"fixed": [
|
||||
"Memory leak in connection pool",
|
||||
"Race condition in concurrent requests"
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.5.0",
|
||||
"date": "2023-12-01",
|
||||
"added": [
|
||||
"Caching layer for improved performance",
|
||||
"Retry mechanism with exponential backoff"
|
||||
],
|
||||
"deprecated": [
|
||||
"`oldMethod()` - use `newMethod()` instead"
|
||||
],
|
||||
"fixed": [
|
||||
"Timeout handling in slow networks",
|
||||
"Unicode encoding issues"
|
||||
],
|
||||
"security": [
|
||||
"Updated crypto library to patch CVE-2023-XXXXX"
|
||||
]
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
System.print("\n" + "=" * 60)
|
||||
System.print("End of Real-World Examples")
|
||||
System.print("=" * 60)
|
||||
61
example/json_demo.wren
vendored
Normal file
61
example/json_demo.wren
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "json" for Json
|
||||
|
||||
System.print("=== Json Module Demo ===\n")
|
||||
|
||||
System.print("--- Parsing JSON ---")
|
||||
var jsonString = "{\"name\":\"Alice\",\"age\":30,\"active\":true}"
|
||||
var obj = Json.parse(jsonString)
|
||||
System.print("Parsed: %(obj)")
|
||||
System.print("Name: %(obj["name"])")
|
||||
System.print("Age: %(obj["age"])")
|
||||
System.print("Active: %(obj["active"])")
|
||||
|
||||
System.print("\n--- Parsing Arrays ---")
|
||||
var arrayJson = "[1, 2, 3, \"four\", true, null]"
|
||||
var arr = Json.parse(arrayJson)
|
||||
System.print("Parsed array: %(arr)")
|
||||
System.print("Third element: %(arr[2])")
|
||||
|
||||
System.print("\n--- Nested Objects ---")
|
||||
var nestedJson = "{\"user\":{\"name\":\"Bob\",\"address\":{\"city\":\"New York\",\"zip\":\"10001\"}},\"tags\":[\"admin\",\"user\"]}"
|
||||
var nested = Json.parse(nestedJson)
|
||||
System.print("City: %(nested["user"]["address"]["city"])")
|
||||
System.print("First tag: %(nested["tags"][0])")
|
||||
|
||||
System.print("\n--- Stringify (compact) ---")
|
||||
var data = {
|
||||
"name": "Charlie",
|
||||
"scores": [85, 92, 78],
|
||||
"metadata": {
|
||||
"created": "2024-01-15",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
System.print(Json.stringify(data))
|
||||
|
||||
System.print("\n--- Stringify (pretty, 2 spaces) ---")
|
||||
System.print(Json.stringify(data, 2))
|
||||
|
||||
System.print("\n--- Stringify (pretty, 4 spaces) ---")
|
||||
System.print(Json.stringify(data, 4))
|
||||
|
||||
System.print("\n--- Special Values ---")
|
||||
var special = {
|
||||
"string": "Hello\nWorld",
|
||||
"number": 42.5,
|
||||
"boolean": false,
|
||||
"null": null,
|
||||
"empty_array": [],
|
||||
"empty_object": {}
|
||||
}
|
||||
System.print(Json.stringify(special, 2))
|
||||
|
||||
System.print("\n--- Round-trip ---")
|
||||
var original = {"key": "value", "list": [1, 2, 3]}
|
||||
var jsonified = Json.stringify(original)
|
||||
var restored = Json.parse(jsonified)
|
||||
System.print("Original: %(original)")
|
||||
System.print("JSON: %(jsonified)")
|
||||
System.print("Restored: %(restored)")
|
||||
78
example/math_demo.wren
vendored
Normal file
78
example/math_demo.wren
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "math" for Math
|
||||
|
||||
System.print("=== Math Module Demo ===\n")
|
||||
|
||||
System.print("--- Constants ---")
|
||||
System.print("pi: %(Math.pi)")
|
||||
System.print("e: %(Math.e)")
|
||||
System.print("tau: %(Math.tau)")
|
||||
System.print("infinity: %(Math.infinity)")
|
||||
System.print("nan: %(Math.nan)")
|
||||
|
||||
System.print("\n--- Trigonometric Functions ---")
|
||||
var angle = Math.pi / 4
|
||||
System.print("Angle: pi/4 radians (45 degrees)")
|
||||
System.print("sin(pi/4): %(Math.sin(angle))")
|
||||
System.print("cos(pi/4): %(Math.cos(angle))")
|
||||
System.print("tan(pi/4): %(Math.tan(angle))")
|
||||
|
||||
System.print("\n--- Inverse Trigonometric Functions ---")
|
||||
System.print("asin(0.5): %(Math.asin(0.5)) radians")
|
||||
System.print("acos(0.5): %(Math.acos(0.5)) radians")
|
||||
System.print("atan(1): %(Math.atan(1)) radians")
|
||||
System.print("atan2(1, 1): %(Math.atan2(1, 1)) radians")
|
||||
|
||||
System.print("\n--- Hyperbolic Functions ---")
|
||||
System.print("sinh(1): %(Math.sinh(1))")
|
||||
System.print("cosh(1): %(Math.cosh(1))")
|
||||
System.print("tanh(1): %(Math.tanh(1))")
|
||||
|
||||
System.print("\n--- Logarithmic Functions ---")
|
||||
System.print("log(e): %(Math.log(Math.e))")
|
||||
System.print("log10(100): %(Math.log10(100))")
|
||||
System.print("log2(8): %(Math.log2(8))")
|
||||
System.print("exp(1): %(Math.exp(1))")
|
||||
|
||||
System.print("\n--- Power and Roots ---")
|
||||
System.print("pow(2, 10): %(Math.pow(2, 10))")
|
||||
System.print("sqrt(16): %(Math.sqrt(16))")
|
||||
System.print("cbrt(27): %(Math.cbrt(27))")
|
||||
|
||||
System.print("\n--- Rounding Functions ---")
|
||||
System.print("ceil(4.3): %(Math.ceil(4.3))")
|
||||
System.print("floor(4.7): %(Math.floor(4.7))")
|
||||
System.print("round(4.5): %(Math.round(4.5))")
|
||||
System.print("abs(-42): %(Math.abs(-42))")
|
||||
|
||||
System.print("\n--- Min, Max, Clamp ---")
|
||||
System.print("min(5, 3): %(Math.min(5, 3))")
|
||||
System.print("max(5, 3): %(Math.max(5, 3))")
|
||||
System.print("clamp(15, 0, 10): %(Math.clamp(15, 0, 10))")
|
||||
System.print("clamp(5, 0, 10): %(Math.clamp(5, 0, 10))")
|
||||
System.print("clamp(-5, 0, 10): %(Math.clamp(-5, 0, 10))")
|
||||
|
||||
System.print("\n--- Sign Function ---")
|
||||
System.print("sign(42): %(Math.sign(42))")
|
||||
System.print("sign(-42): %(Math.sign(-42))")
|
||||
System.print("sign(0): %(Math.sign(0))")
|
||||
|
||||
System.print("\n--- Linear Interpolation ---")
|
||||
System.print("lerp(0, 100, 0.0): %(Math.lerp(0, 100, 0))")
|
||||
System.print("lerp(0, 100, 0.5): %(Math.lerp(0, 100, 0.5))")
|
||||
System.print("lerp(0, 100, 1.0): %(Math.lerp(0, 100, 1))")
|
||||
|
||||
System.print("\n--- Angle Conversion ---")
|
||||
System.print("degrees(pi): %(Math.degrees(Math.pi))")
|
||||
System.print("degrees(pi/2): %(Math.degrees(Math.pi / 2))")
|
||||
System.print("radians(180): %(Math.radians(180))")
|
||||
System.print("radians(90): %(Math.radians(90))")
|
||||
|
||||
System.print("\n--- Practical Example: Distance Calculation ---")
|
||||
var x1 = 0
|
||||
var y1 = 0
|
||||
var x2 = 3
|
||||
var y2 = 4
|
||||
var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
||||
System.print("Distance from (%(x1),%(y1)) to (%(x2),%(y2)): %(distance)")
|
||||
138
example/openrouter_chat.wren
vendored
Normal file
138
example/openrouter_chat.wren
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "http" for Http
|
||||
import "json" for Json
|
||||
import "env" for Environment
|
||||
import "io" for Stdin, Stdout
|
||||
|
||||
class Chat {
|
||||
construct new(model) {
|
||||
_model = model
|
||||
_messages = []
|
||||
_apiKey = Environment.get("OPENROUTER_API_KEY")
|
||||
if (_apiKey == null || _apiKey.count == 0) {
|
||||
Fiber.abort("OPENROUTER_API_KEY environment variable not set")
|
||||
}
|
||||
}
|
||||
|
||||
addSystem(content) {
|
||||
_messages.add({"role": "system", "content": content})
|
||||
}
|
||||
|
||||
send(content) {
|
||||
_messages.add({"role": "user", "content": content})
|
||||
|
||||
var requestBody = {
|
||||
"model": _model,
|
||||
"messages": _messages,
|
||||
"stream": false
|
||||
}
|
||||
|
||||
var headers = {
|
||||
"Authorization": "Bearer " + _apiKey,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
var response = Http.post("https://openrouter.ai/api/v1/chat/completions", requestBody, headers)
|
||||
|
||||
if (!response.ok) {
|
||||
var error = "API error: %(response.statusCode)"
|
||||
if (response.body.count > 0) {
|
||||
var data = Json.parse(response.body)
|
||||
if (data["error"] != null && data["error"]["message"] != null) {
|
||||
error = data["error"]["message"]
|
||||
}
|
||||
}
|
||||
return {"error": error}
|
||||
}
|
||||
|
||||
var data = Json.parse(response.body)
|
||||
if (data["choices"] != null && data["choices"].count > 0) {
|
||||
var message = data["choices"][0]["message"]
|
||||
if (message != null) {
|
||||
_messages.add(message)
|
||||
return {
|
||||
"content": message["content"],
|
||||
"model": data["model"],
|
||||
"usage": data["usage"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {"error": "Invalid response format"}
|
||||
}
|
||||
|
||||
clear() {
|
||||
var system = _messages.count > 0 && _messages[0]["role"] == "system" ? _messages[0] : null
|
||||
_messages = []
|
||||
if (system != null) _messages.add(system)
|
||||
}
|
||||
|
||||
model { _model }
|
||||
model=(value) { _model = value }
|
||||
messageCount { _messages.count }
|
||||
}
|
||||
|
||||
var model = "x-ai/grok-code-fast-1"
|
||||
var chat = Chat.new(model)
|
||||
|
||||
chat.addSystem("You are a helpful assistant. Be concise in your responses.")
|
||||
|
||||
System.print("OpenRouter Chat")
|
||||
System.print("Model: %(model)")
|
||||
System.print("Commands: /quit, /clear, /model <name>, /history")
|
||||
System.print("-" * 50)
|
||||
|
||||
while (true) {
|
||||
System.write("\nYou: ")
|
||||
Stdout.flush()
|
||||
var input = Stdin.readLine()
|
||||
|
||||
if (input == null || input.trim() == "/quit" || input.trim() == "/exit") {
|
||||
System.print("Goodbye!")
|
||||
break
|
||||
}
|
||||
|
||||
input = input.trim()
|
||||
if (input.count == 0) continue
|
||||
|
||||
if (input == "/clear") {
|
||||
chat.clear()
|
||||
System.print("[Conversation cleared]")
|
||||
continue
|
||||
}
|
||||
|
||||
if (input == "/history") {
|
||||
System.print("[%(chat.messageCount) messages in history]")
|
||||
continue
|
||||
}
|
||||
|
||||
if (input.startsWith("/model ")) {
|
||||
var newModel = input[7..-1].trim()
|
||||
if (newModel.count > 0) {
|
||||
chat.model = newModel
|
||||
System.print("[Model changed to: %(newModel)]")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (input.startsWith("/")) {
|
||||
System.print("[Unknown command: %(input)]")
|
||||
continue
|
||||
}
|
||||
|
||||
System.write("\nAssistant: ")
|
||||
Stdout.flush()
|
||||
|
||||
var result = chat.send(input)
|
||||
|
||||
if (result["error"] != null) {
|
||||
System.print("[Error: %(result["error"])]")
|
||||
} else {
|
||||
System.print(result["content"])
|
||||
if (result["usage"] != null) {
|
||||
var usage = result["usage"]
|
||||
System.print("\n[tokens: %(usage["prompt_tokens"]) prompt, %(usage["completion_tokens"]) completion]")
|
||||
}
|
||||
}
|
||||
}
|
||||
55
example/openrouter_demo.wren
vendored
Normal file
55
example/openrouter_demo.wren
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "http" for Http
|
||||
import "json" for Json
|
||||
import "env" for Environment
|
||||
|
||||
var apiKey = Environment.get("OPENROUTER_API_KEY")
|
||||
if (apiKey == null || apiKey.count == 0) {
|
||||
System.print("Error: OPENROUTER_API_KEY environment variable not set")
|
||||
Fiber.abort("Missing API key")
|
||||
}
|
||||
|
||||
System.print("=== OpenRouter API Demo ===\n")
|
||||
|
||||
var model = "x-ai/grok-code-fast-1"
|
||||
var prompt = "What is 2 + 2? Reply with just the number."
|
||||
|
||||
System.print("Model: %(model)")
|
||||
System.print("Prompt: %(prompt)")
|
||||
System.print("")
|
||||
|
||||
var requestBody = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
}
|
||||
|
||||
var headers = {
|
||||
"Authorization": "Bearer " + apiKey,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
System.print("Sending request to OpenRouter...")
|
||||
|
||||
var response = Http.post("https://openrouter.ai/api/v1/chat/completions", requestBody, headers)
|
||||
|
||||
System.print("Status: %(response.statusCode)")
|
||||
System.print("")
|
||||
|
||||
if (response.ok) {
|
||||
var data = Json.parse(response.body)
|
||||
if (data != null && data["choices"] != null && data["choices"].count > 0) {
|
||||
var message = data["choices"][0]["message"]
|
||||
if (message != null && message["content"] != null) {
|
||||
System.print("Response: %(message["content"])")
|
||||
}
|
||||
}
|
||||
System.print("\nModel used: %(data["model"])")
|
||||
if (data["usage"] != null) {
|
||||
System.print("Tokens - prompt: %(data["usage"]["prompt_tokens"]), completion: %(data["usage"]["completion_tokens"])")
|
||||
}
|
||||
} else {
|
||||
System.print("Error: %(response.body)")
|
||||
}
|
||||
130
example/regex_demo.wren
vendored
Normal file
130
example/regex_demo.wren
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "regex" for Regex
|
||||
|
||||
System.print("=== Regex Module Demo ===\n")
|
||||
|
||||
System.print("--- Basic Pattern Matching ---")
|
||||
var pattern = Regex.new("hello")
|
||||
System.print("Pattern: %(pattern.pattern)")
|
||||
System.print("Test 'hello world': %(pattern.test("hello world"))")
|
||||
System.print("Test 'HELLO world': %(pattern.test("HELLO world"))")
|
||||
System.print("Test 'goodbye': %(pattern.test("goodbye"))")
|
||||
|
||||
System.print("\n--- Case Insensitive Matching ---")
|
||||
var insensitive = Regex.new("hello", "i")
|
||||
System.print("Pattern: %(insensitive.pattern) (flags: %(insensitive.flags))")
|
||||
System.print("Test 'HELLO world': %(insensitive.test("HELLO world"))")
|
||||
System.print("Test 'HeLLo': %(insensitive.test("HeLLo"))")
|
||||
|
||||
System.print("\n--- Digit Patterns ---")
|
||||
var digitPattern = Regex.new("[0-9]+")
|
||||
System.print("Pattern: [0-9]+")
|
||||
System.print("Test '123': %(digitPattern.test("123"))")
|
||||
System.print("Test 'abc': %(digitPattern.test("abc"))")
|
||||
System.print("Test 'abc123': %(digitPattern.test("abc123"))")
|
||||
|
||||
System.print("\n--- Replace First Match ---")
|
||||
var text = "hello hello hello"
|
||||
var helloPattern = Regex.new("hello")
|
||||
var replaced = helloPattern.replace(text, "hi")
|
||||
System.print("Original: %(text)")
|
||||
System.print("Replaced first: %(replaced)")
|
||||
|
||||
System.print("\n--- Replace All Matches ---")
|
||||
var replacedAll = helloPattern.replaceAll(text, "hi")
|
||||
System.print("Replaced all: %(replacedAll)")
|
||||
|
||||
System.print("\n--- Split by Pattern ---")
|
||||
var csv = "apple,banana,cherry,date"
|
||||
var commaPattern = Regex.new(",")
|
||||
var parts = commaPattern.split(csv)
|
||||
System.print("CSV: %(csv)")
|
||||
System.print("Split parts: %(parts)")
|
||||
|
||||
System.print("\n--- Split by Multiple Commas ---")
|
||||
var csvMultiple = "apple,,banana,,,cherry"
|
||||
var multiComma = Regex.new(",+")
|
||||
var cleanParts = multiComma.split(csvMultiple)
|
||||
System.print("CSV: %(csvMultiple)")
|
||||
System.print("Split parts: %(cleanParts)")
|
||||
|
||||
System.print("\n--- Word Pattern ---")
|
||||
var wordPattern = Regex.new("[a-zA-Z]+")
|
||||
System.print("Test 'hello123': %(wordPattern.test("hello123"))")
|
||||
System.print("Test '12345': %(wordPattern.test("12345"))")
|
||||
|
||||
System.print("\n--- Whitespace Handling ---")
|
||||
var whitespace = Regex.new("\\s+")
|
||||
var messy = "too many spaces"
|
||||
var cleaned = whitespace.replaceAll(messy, " ")
|
||||
System.print("Original: '%(messy)'")
|
||||
System.print("Cleaned: '%(cleaned)'")
|
||||
|
||||
System.print("\n--- Special Character Escapes ---")
|
||||
var digitEscape = Regex.new("\\d+")
|
||||
System.print("Pattern: \\d+ (digits)")
|
||||
System.print("Test 'abc123def': %(digitEscape.test("abc123def"))")
|
||||
|
||||
var wordEscape = Regex.new("\\w+")
|
||||
System.print("Pattern: \\w+ (word chars)")
|
||||
System.print("Test 'hello_world': %(wordEscape.test("hello_world"))")
|
||||
|
||||
var spaceEscape = Regex.new("\\s")
|
||||
System.print("Pattern: \\s (whitespace)")
|
||||
System.print("Test 'hello world': %(spaceEscape.test("hello world"))")
|
||||
|
||||
System.print("\n--- Anchors ---")
|
||||
var startAnchor = Regex.new("^hello")
|
||||
System.print("Pattern: ^hello (start anchor)")
|
||||
System.print("Test 'hello world': %(startAnchor.test("hello world"))")
|
||||
System.print("Test 'say hello': %(startAnchor.test("say hello"))")
|
||||
|
||||
var endAnchor = Regex.new("world$")
|
||||
System.print("Pattern: world$ (end anchor)")
|
||||
System.print("Test 'hello world': %(endAnchor.test("hello world"))")
|
||||
System.print("Test 'world hello': %(endAnchor.test("world hello"))")
|
||||
|
||||
System.print("\n--- Quantifiers ---")
|
||||
var optional = Regex.new("colou?r")
|
||||
System.print("Pattern: colou?r (optional u)")
|
||||
System.print("Test 'color': %(optional.test("color"))")
|
||||
System.print("Test 'colour': %(optional.test("colour"))")
|
||||
|
||||
var oneOrMore = Regex.new("a+")
|
||||
System.print("Pattern: a+ (one or more a)")
|
||||
System.print("Test 'aaa': %(oneOrMore.test("aaa"))")
|
||||
System.print("Test 'bbb': %(oneOrMore.test("bbb"))")
|
||||
|
||||
var zeroOrMore = Regex.new("ba*b")
|
||||
System.print("Pattern: ba*b (zero or more a)")
|
||||
System.print("Test 'bb': %(zeroOrMore.test("bb"))")
|
||||
System.print("Test 'bab': %(zeroOrMore.test("bab"))")
|
||||
System.print("Test 'baaab': %(zeroOrMore.test("baaab"))")
|
||||
|
||||
System.print("\n--- Character Classes ---")
|
||||
var vowels = Regex.new("[aeiou]")
|
||||
System.print("Pattern: [aeiou] (vowels)")
|
||||
System.print("Test 'hello': %(vowels.test("hello"))")
|
||||
System.print("Test 'xyz': %(vowels.test("xyz"))")
|
||||
|
||||
var notDigit = Regex.new("[^0-9]+")
|
||||
System.print("Pattern: [^0-9]+ (non-digits)")
|
||||
var numbersRemoved = notDigit.replaceAll("abc123def456", "")
|
||||
System.print("Remove non-digits from 'abc123def456': '%(numbersRemoved)'")
|
||||
|
||||
System.print("\n--- Practical: Trim Whitespace ---")
|
||||
var leadingSpace = Regex.new("^\\s+")
|
||||
var trailingSpace = Regex.new("\\s+$")
|
||||
var padded = " hello world "
|
||||
var trimmed = leadingSpace.replace(padded, "")
|
||||
trimmed = trailingSpace.replace(trimmed, "")
|
||||
System.print("Original: '[%(padded)]'")
|
||||
System.print("Trimmed: '[%(trimmed)]'")
|
||||
|
||||
System.print("\n--- Practical: Simple Tokenizer ---")
|
||||
var tokenSeparator = Regex.new("[\\s,;]+")
|
||||
var sentence = "hello, world; foo bar"
|
||||
var tokens = tokenSeparator.split(sentence)
|
||||
System.print("Input: %(sentence)")
|
||||
System.print("Tokens: %(tokens)")
|
||||
39
example/signal_demo.wren
vendored
Normal file
39
example/signal_demo.wren
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "signal" for Signal
|
||||
|
||||
System.print("=== Signal Module Demo ===\n")
|
||||
|
||||
System.print("--- Available Signal Constants ---")
|
||||
System.print("SIGHUP: %(Signal.SIGHUP)")
|
||||
System.print("SIGINT: %(Signal.SIGINT)")
|
||||
System.print("SIGQUIT: %(Signal.SIGQUIT)")
|
||||
System.print("SIGTERM: %(Signal.SIGTERM)")
|
||||
System.print("SIGUSR1: %(Signal.SIGUSR1)")
|
||||
System.print("SIGUSR2: %(Signal.SIGUSR2)")
|
||||
|
||||
System.print("\n--- Signal API Overview ---")
|
||||
System.print("The Signal module provides:")
|
||||
System.print(" Signal.trap(signum, fn) - Set a handler for a signal")
|
||||
System.print(" Signal.ignore(signum) - Ignore a signal")
|
||||
System.print(" Signal.reset(signum) - Reset to default behavior")
|
||||
|
||||
System.print("\n--- Example Usage ---")
|
||||
System.print("To handle SIGINT (Ctrl+C):")
|
||||
System.print(" Signal.trap(Signal.SIGINT) {")
|
||||
System.print(" System.print(\"Interrupted!\")")
|
||||
System.print(" }")
|
||||
|
||||
System.print("\nTo ignore SIGTERM:")
|
||||
System.print(" Signal.ignore(Signal.SIGTERM)")
|
||||
|
||||
System.print("\nTo reset SIGHUP to default:")
|
||||
System.print(" Signal.reset(Signal.SIGHUP)")
|
||||
|
||||
System.print("\n--- Notes ---")
|
||||
System.print("Signal handling is useful for:")
|
||||
System.print(" - Graceful shutdown on SIGTERM/SIGINT")
|
||||
System.print(" - Reloading configuration on SIGHUP")
|
||||
System.print(" - Custom actions on SIGUSR1/SIGUSR2")
|
||||
|
||||
System.print("\n--- Demo Complete ---")
|
||||
112
example/sqlite_demo.wren
vendored
Normal file
112
example/sqlite_demo.wren
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "sqlite" for Database
|
||||
|
||||
System.print("=== SQLite Module Demo ===\n")
|
||||
|
||||
System.print("--- Creating In-Memory Database ---")
|
||||
var db = Database.memory()
|
||||
System.print("Database opened successfully")
|
||||
|
||||
System.print("\n--- Creating Table ---")
|
||||
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT, age INTEGER)")
|
||||
System.print("Table 'users' created")
|
||||
|
||||
System.print("\n--- Inserting Data ---")
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES ('Alice', 'alice@example.com', 30)")
|
||||
System.print("Last insert ID: %(db.lastInsertId)")
|
||||
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES ('Bob', 'bob@example.com', 25)")
|
||||
System.print("Last insert ID: %(db.lastInsertId)")
|
||||
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES ('Charlie', 'charlie@example.com', 35)")
|
||||
System.print("Last insert ID: %(db.lastInsertId)")
|
||||
|
||||
System.print("Rows changed: %(db.changes)")
|
||||
|
||||
System.print("\n--- Parameterized Insert ---")
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Diana", "diana@example.com", 28])
|
||||
System.print("Inserted Diana with ID: %(db.lastInsertId)")
|
||||
|
||||
System.print("\n--- Querying All Data ---")
|
||||
var allUsers = db.query("SELECT * FROM users")
|
||||
System.print("All users:")
|
||||
for (user in allUsers) {
|
||||
System.print(" %(user["id"]): %(user["name"]) <%(user["email"])> age %(user["age"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Parameterized Query ---")
|
||||
var olderUsers = db.query("SELECT * FROM users WHERE age > ?", [27])
|
||||
System.print("Users older than 27:")
|
||||
for (user in olderUsers) {
|
||||
System.print(" %(user["name"]) (age %(user["age"]))")
|
||||
}
|
||||
|
||||
System.print("\n--- Single Result Query ---")
|
||||
var singleUser = db.query("SELECT * FROM users WHERE name = ?", ["Alice"])
|
||||
if (singleUser.count > 0) {
|
||||
System.print("Found Alice: %(singleUser[0]["email"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Aggregate Query ---")
|
||||
var avgAge = db.query("SELECT AVG(age) as avg_age, COUNT(*) as count FROM users")
|
||||
System.print("Average age: %(avgAge[0]["avg_age"])")
|
||||
System.print("Total users: %(avgAge[0]["count"])")
|
||||
|
||||
System.print("\n--- Updating Data ---")
|
||||
db.execute("UPDATE users SET age = ? WHERE name = ?", [31, "Alice"])
|
||||
System.print("Rows updated: %(db.changes)")
|
||||
|
||||
var updated = db.query("SELECT age FROM users WHERE name = 'Alice'")
|
||||
System.print("Alice's new age: %(updated[0]["age"])")
|
||||
|
||||
System.print("\n--- Deleting Data ---")
|
||||
db.execute("DELETE FROM users WHERE name = ?", ["Charlie"])
|
||||
System.print("Rows deleted: %(db.changes)")
|
||||
|
||||
System.print("\n--- Remaining Users ---")
|
||||
var remaining = db.query("SELECT name FROM users ORDER BY name")
|
||||
System.print("Users: %(remaining)")
|
||||
|
||||
System.print("\n--- Creating Another Table ---")
|
||||
db.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, product TEXT, amount REAL)")
|
||||
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (1, 'Widget', 19.99)")
|
||||
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (1, 'Gadget', 29.99)")
|
||||
db.execute("INSERT INTO orders (user_id, product, amount) VALUES (2, 'Widget', 19.99)")
|
||||
|
||||
System.print("\n--- Join Query ---")
|
||||
var ordersWithUsers = db.query("SELECT u.name, o.product, o.amount FROM users u JOIN orders o ON u.id = o.user_id")
|
||||
System.print("Orders with user names:")
|
||||
for (row in ordersWithUsers) {
|
||||
System.print(" %(row["name"]) ordered %(row["product"]) for $%(row["amount"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Transaction Example ---")
|
||||
db.execute("BEGIN TRANSACTION")
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Eve", "eve@example.com", 22])
|
||||
db.execute("INSERT INTO users (name, email, age) VALUES (?, ?, ?)", ["Frank", "frank@example.com", 45])
|
||||
db.execute("COMMIT")
|
||||
System.print("Transaction committed")
|
||||
|
||||
var afterTransaction = db.query("SELECT COUNT(*) as count FROM users")
|
||||
System.print("Total users after transaction: %(afterTransaction[0]["count"])")
|
||||
|
||||
System.print("\n--- Closing Database ---")
|
||||
db.close()
|
||||
System.print("Database closed")
|
||||
|
||||
System.print("\n--- File-based Database Example ---")
|
||||
var fileDb = Database.open("/tmp/wren_demo.db")
|
||||
fileDb.execute("CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)")
|
||||
fileDb.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", ["version", "1.0.0"])
|
||||
fileDb.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", ["theme", "dark"])
|
||||
|
||||
var settings = fileDb.query("SELECT * FROM settings")
|
||||
System.print("Settings:")
|
||||
for (s in settings) {
|
||||
System.print(" %(s["key"]) = %(s["value"])")
|
||||
}
|
||||
|
||||
fileDb.close()
|
||||
System.print("File database closed")
|
||||
System.print("Database saved to: /tmp/wren_demo.db")
|
||||
70
example/subprocess_demo.wren
vendored
Normal file
70
example/subprocess_demo.wren
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "subprocess" for Subprocess, ProcessResult
|
||||
|
||||
System.print("=== Subprocess Module Demo ===\n")
|
||||
|
||||
System.print("--- Running Shell Commands ---")
|
||||
var result = Subprocess.run("echo 'Hello from subprocess!'")
|
||||
System.print("stdout: %(result.stdout.trim())")
|
||||
System.print("exit code: %(result.exitCode)")
|
||||
System.print("success: %(result.success)")
|
||||
|
||||
System.print("\n--- Running with Arguments List ---")
|
||||
var lsResult = Subprocess.run(["/bin/ls", "-la", "/tmp"])
|
||||
System.print("Directory listing (first 200 chars):")
|
||||
var output = lsResult.stdout
|
||||
if (output.count > 200) output = output[0...200] + "..."
|
||||
System.print(output)
|
||||
|
||||
System.print("\n--- Getting Current Directory ---")
|
||||
var pwdResult = Subprocess.run("pwd")
|
||||
System.print("Current directory: %(pwdResult.stdout.trim())")
|
||||
|
||||
System.print("\n--- Environment Variables ---")
|
||||
var envResult = Subprocess.run("echo $USER@$HOSTNAME")
|
||||
System.print("User info: %(envResult.stdout.trim())")
|
||||
|
||||
System.print("\n--- Piped Commands ---")
|
||||
var pipeResult = Subprocess.run("echo 'apple\nbanana\ncherry' | sort -r")
|
||||
System.print("Sorted output:")
|
||||
System.print(pipeResult.stdout)
|
||||
|
||||
System.print("--- Command with Stderr ---")
|
||||
var errResult = Subprocess.run("ls /nonexistent_directory_12345 2>&1")
|
||||
System.print("Output: %(errResult.stdout.trim())")
|
||||
System.print("Exit code: %(errResult.exitCode)")
|
||||
System.print("Success: %(errResult.success)")
|
||||
|
||||
System.print("\n--- Capturing Both Stdout and Stderr ---")
|
||||
var mixedResult = Subprocess.run("echo 'stdout message' && echo 'stderr message' >&2")
|
||||
System.print("stdout: %(mixedResult.stdout.trim())")
|
||||
System.print("stderr: %(mixedResult.stderr.trim())")
|
||||
|
||||
System.print("\n--- Running Python (if available) ---")
|
||||
var pyResult = Subprocess.run("python3 -c \"print('Hello from Python!')\" 2>/dev/null || echo 'Python not available'")
|
||||
System.print("Python: %(pyResult.stdout.trim())")
|
||||
|
||||
System.print("\n--- Getting System Info ---")
|
||||
var unameResult = Subprocess.run("uname -a")
|
||||
System.print("System: %(unameResult.stdout.trim())")
|
||||
|
||||
System.print("\n--- Date and Time ---")
|
||||
var dateResult = Subprocess.run("date '+\%Y-\%m-\%d \%H:\%M:\%S'")
|
||||
System.print("Current time: %(dateResult.stdout.trim())")
|
||||
|
||||
System.print("\n--- Checking Exit Codes ---")
|
||||
var trueResult = Subprocess.run("true")
|
||||
var falseResult = Subprocess.run("false")
|
||||
System.print("'true' exit code: %(trueResult.exitCode)")
|
||||
System.print("'false' exit code: %(falseResult.exitCode)")
|
||||
|
||||
System.print("\n--- Practical Example: Git Status ---")
|
||||
var gitResult = Subprocess.run("git status --short 2>/dev/null || echo 'Not a git repository'")
|
||||
System.print("Git status:")
|
||||
var gitOutput = gitResult.stdout.trim()
|
||||
if (gitOutput.count == 0) {
|
||||
System.print(" (clean working directory)")
|
||||
} else {
|
||||
System.print(gitOutput)
|
||||
}
|
||||
49
example/websocket_chat_client.wren
vendored
Normal file
49
example/websocket_chat_client.wren
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocket
|
||||
import "scheduler" for Scheduler
|
||||
import "timer" for Timer
|
||||
import "io" for Stdin
|
||||
|
||||
var host = "ws://127.0.0.1:8080"
|
||||
|
||||
System.print("=== WebSocket Chat Client ===")
|
||||
System.print("Connecting to %(host)...")
|
||||
|
||||
var ws = WebSocket.connect(host)
|
||||
System.print("Connected!")
|
||||
System.print("Commands: /name <name>, /who, /quit")
|
||||
System.print("Type a message and press Enter to send.\n")
|
||||
|
||||
var running = true
|
||||
|
||||
Scheduler.add {
|
||||
while (running && ws.isOpen) {
|
||||
var msg = ws.receive()
|
||||
if (msg == null || msg.isClose) {
|
||||
running = false
|
||||
System.print("\nConnection closed.")
|
||||
break
|
||||
}
|
||||
if (msg.isText) {
|
||||
System.print(msg.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer.sleep(100)
|
||||
|
||||
while (running && ws.isOpen) {
|
||||
System.write("> ")
|
||||
var line = Stdin.readLine()
|
||||
if (line == null || line == "/quit") {
|
||||
running = false
|
||||
break
|
||||
}
|
||||
if (line.count > 0) {
|
||||
ws.send(line)
|
||||
}
|
||||
}
|
||||
|
||||
ws.close()
|
||||
System.print("Disconnected.")
|
||||
95
example/websocket_chat_server.wren
vendored
Normal file
95
example/websocket_chat_server.wren
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocket, WebSocketServer
|
||||
import "scheduler" for Scheduler
|
||||
import "timer" for Timer
|
||||
|
||||
var port = 8080
|
||||
var clients = []
|
||||
var clientId = 0
|
||||
|
||||
System.print("=== WebSocket Chat Server ===")
|
||||
System.print("Listening on ws://0.0.0.0:%(port)")
|
||||
System.print("Clients can send messages that get broadcast to all others.\n")
|
||||
|
||||
var broadcast = Fn.new { |sender, message|
|
||||
var i = 0
|
||||
while (i < clients.count) {
|
||||
var client = clients[i]
|
||||
if (client["ws"].isOpen && client["id"] != sender) {
|
||||
client["ws"].send(message)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
var removeClient = Fn.new { |id|
|
||||
var i = 0
|
||||
while (i < clients.count) {
|
||||
if (clients[i]["id"] == id) {
|
||||
clients.removeAt(i)
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
var handleClient = Fn.new { |ws, id|
|
||||
var client = {"ws": ws, "id": id, "name": "User%(id)"}
|
||||
clients.add(client)
|
||||
|
||||
System.print("[%(client["name"])] Connected (%(clients.count) clients online)")
|
||||
broadcast.call(id, "*** %(client["name"]) joined the chat ***")
|
||||
|
||||
while (ws.isOpen) {
|
||||
var msg = ws.receive()
|
||||
|
||||
if (msg == null || msg.isClose) {
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isText) {
|
||||
var text = msg.text
|
||||
|
||||
if (text.startsWith("/name ")) {
|
||||
var oldName = client["name"]
|
||||
client["name"] = text[6..-1]
|
||||
broadcast.call(-1, "*** %(oldName) is now known as %(client["name"]) ***")
|
||||
ws.send("Name changed to %(client["name"])")
|
||||
} else if (text == "/who") {
|
||||
var names = []
|
||||
for (c in clients) {
|
||||
names.add(c["name"])
|
||||
}
|
||||
ws.send("Online: " + names.join(", "))
|
||||
} else if (text == "/quit") {
|
||||
ws.send("Goodbye!")
|
||||
break
|
||||
} else {
|
||||
var chatMsg = "[%(client["name"])]: %(text)"
|
||||
System.print(chatMsg)
|
||||
broadcast.call(id, chatMsg)
|
||||
ws.send(chatMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeClient.call(id)
|
||||
System.print("[%(client["name"])] Disconnected (%(clients.count) clients online)")
|
||||
broadcast.call(id, "*** %(client["name"]) left the chat ***")
|
||||
}
|
||||
|
||||
var server = WebSocketServer.bind("0.0.0.0", port)
|
||||
|
||||
while (true) {
|
||||
var ws = server.accept()
|
||||
|
||||
if (ws != null) {
|
||||
clientId = clientId + 1
|
||||
var id = clientId
|
||||
|
||||
Scheduler.add {
|
||||
handleClient.call(ws, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
example/websocket_concurrent_server.wren
vendored
Normal file
69
example/websocket_concurrent_server.wren
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocket, WebSocketServer
|
||||
import "scheduler" for Scheduler
|
||||
import "timer" for Timer
|
||||
|
||||
var port = 8080
|
||||
var clientCount = 0
|
||||
var messageCount = 0
|
||||
|
||||
System.print("=== Concurrent WebSocket Server ===")
|
||||
System.print("Listening on ws://0.0.0.0:%(port)")
|
||||
System.print("Each client connection runs in its own fiber.\n")
|
||||
|
||||
var server = WebSocketServer.bind("0.0.0.0", port)
|
||||
|
||||
var handleClient = Fn.new { |ws, clientId|
|
||||
System.print("[Client %(clientId)] Connected")
|
||||
|
||||
while (ws.isOpen) {
|
||||
var msg = ws.receive()
|
||||
|
||||
if (msg == null) {
|
||||
System.print("[Client %(clientId)] Connection lost")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isClose) {
|
||||
System.print("[Client %(clientId)] Sent close frame")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isText) {
|
||||
messageCount = messageCount + 1
|
||||
var text = msg.text
|
||||
System.print("[Client %(clientId)] Received: %(text) (total messages: %(messageCount))")
|
||||
|
||||
if (text == "ping") {
|
||||
ws.send("pong")
|
||||
} else if (text == "stats") {
|
||||
ws.send("clients: %(clientCount), messages: %(messageCount)")
|
||||
} else if (text == "broadcast") {
|
||||
ws.send("broadcast not implemented in this example")
|
||||
} else {
|
||||
ws.send("echo: %(text)")
|
||||
}
|
||||
} else if (msg.isBinary) {
|
||||
messageCount = messageCount + 1
|
||||
System.print("[Client %(clientId)] Received binary: %(msg.bytes.count) bytes")
|
||||
ws.sendBinary(msg.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
clientCount = clientCount - 1
|
||||
System.print("[Client %(clientId)] Disconnected (%(clientCount) clients remaining)")
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var ws = server.accept()
|
||||
|
||||
if (ws != null) {
|
||||
clientCount = clientCount + 1
|
||||
var clientId = clientCount
|
||||
|
||||
Scheduler.add {
|
||||
handleClient.call(ws, clientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
23
example/websocket_demo.wren
vendored
Normal file
23
example/websocket_demo.wren
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocket
|
||||
|
||||
System.print("=== WebSocket Client Demo ===\n")
|
||||
|
||||
System.print("Connecting to ws://127.0.0.1:8080...")
|
||||
var ws = WebSocket.connect("ws://127.0.0.1:8080")
|
||||
System.print("Connected\n")
|
||||
|
||||
System.print("--- Sending Text Message ---")
|
||||
ws.send("Hello WebSocket!")
|
||||
var msg = ws.receive()
|
||||
System.print("Received: %(msg.text)")
|
||||
|
||||
System.print("\n--- Sending Binary Message ---")
|
||||
ws.sendBinary([72, 101, 108, 108, 111])
|
||||
msg = ws.receive()
|
||||
System.print("Received bytes: %(msg.bytes)")
|
||||
|
||||
System.print("\n--- Closing Connection ---")
|
||||
ws.close(1000, "Client closing")
|
||||
System.print("Connection closed")
|
||||
91
example/websocket_load_test.wren
vendored
Normal file
91
example/websocket_load_test.wren
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocket
|
||||
import "io" for Stdout
|
||||
|
||||
var host = "ws://127.0.0.1:8080"
|
||||
var totalMessages = 50000
|
||||
var warmupMessages = 100
|
||||
|
||||
System.print("=== WebSocket Extreme Load Test ===")
|
||||
System.print("Target: %(host)")
|
||||
System.print("Messages: %(totalMessages) (warmup: %(warmupMessages))")
|
||||
System.print("")
|
||||
Stdout.flush()
|
||||
|
||||
var ws = WebSocket.connect(host)
|
||||
System.print("Connected")
|
||||
Stdout.flush()
|
||||
|
||||
var i = 0
|
||||
while (i < warmupMessages) {
|
||||
ws.send("warmup%(i)")
|
||||
ws.receive()
|
||||
i = i + 1
|
||||
}
|
||||
System.print("Warmup complete")
|
||||
Stdout.flush()
|
||||
|
||||
var errors = 0
|
||||
var minTime = 999999
|
||||
var maxTime = 0
|
||||
var totalTime = 0
|
||||
var times = []
|
||||
|
||||
var startTime = System.clock
|
||||
|
||||
i = 0
|
||||
while (i < totalMessages) {
|
||||
var msgStart = System.clock
|
||||
ws.send("msg%(i)")
|
||||
var response = ws.receive()
|
||||
var msgEnd = System.clock
|
||||
var elapsed = msgEnd - msgStart
|
||||
|
||||
if (response == null || !response.isText) {
|
||||
errors = errors + 1
|
||||
} else {
|
||||
times.add(elapsed)
|
||||
totalTime = totalTime + elapsed
|
||||
if (elapsed < minTime) minTime = elapsed
|
||||
if (elapsed > maxTime) maxTime = elapsed
|
||||
}
|
||||
|
||||
if (i > 0 && i % 2000 == 0) {
|
||||
var progress = (i * 100 / totalMessages).floor
|
||||
var currentRate = i / (System.clock - startTime)
|
||||
System.print(" Progress: %(progress) percent (%(currentRate.floor) msg/sec)")
|
||||
Stdout.flush()
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
var endTime = System.clock
|
||||
var totalElapsed = endTime - startTime
|
||||
|
||||
ws.close()
|
||||
|
||||
times.sort()
|
||||
var p50 = times[(times.count * 0.5).floor]
|
||||
var p90 = times[(times.count * 0.9).floor]
|
||||
var p95 = times[(times.count * 0.95).floor]
|
||||
var p99 = times[(times.count * 0.99).floor]
|
||||
var avgTime = totalTime / times.count
|
||||
|
||||
System.print("")
|
||||
System.print("=== Results ===")
|
||||
System.print("Total messages: %(totalMessages)")
|
||||
System.print("Errors: %(errors)")
|
||||
System.print("Total time: %(totalElapsed)s")
|
||||
System.print("Throughput: %((totalMessages / totalElapsed).floor) msg/sec")
|
||||
System.print("")
|
||||
System.print("=== Latency (per message round-trip) ===")
|
||||
System.print("Min: %((minTime * 1000000).floor) us")
|
||||
System.print("Max: %((maxTime * 1000000).floor) us")
|
||||
System.print("Avg: %((avgTime * 1000000).floor) us")
|
||||
System.print("P50: %((p50 * 1000000).floor) us")
|
||||
System.print("P90: %((p90 * 1000000).floor) us")
|
||||
System.print("P95: %((p95 * 1000000).floor) us")
|
||||
System.print("P99: %((p99 * 1000000).floor) us")
|
||||
Stdout.flush()
|
||||
44
example/websocket_server_demo.wren
vendored
Normal file
44
example/websocket_server_demo.wren
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "websocket" for WebSocketServer
|
||||
|
||||
System.print("=== WebSocket Server Demo ===")
|
||||
System.print("Listening on ws://0.0.0.0:8080\n")
|
||||
|
||||
var server = WebSocketServer.bind("0.0.0.0", 8080)
|
||||
|
||||
while (true) {
|
||||
System.print("Waiting for connection...")
|
||||
var ws = server.accept()
|
||||
|
||||
if (ws == null) {
|
||||
System.print("Failed to accept connection")
|
||||
continue
|
||||
}
|
||||
|
||||
System.print("Client connected")
|
||||
|
||||
while (ws.isOpen) {
|
||||
var msg = ws.receive()
|
||||
|
||||
if (msg == null) {
|
||||
System.print("Connection lost")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isClose) {
|
||||
System.print("Client sent close frame (code: %(msg.closeCode))")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isText) {
|
||||
System.print("Received text: %(msg.text)")
|
||||
ws.send("Echo: %(msg.text)")
|
||||
} else if (msg.isBinary) {
|
||||
System.print("Received binary: %(msg.bytes.count) bytes")
|
||||
ws.sendBinary(msg.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
System.print("Client disconnected\n")
|
||||
}
|
||||
@ -19,11 +19,11 @@ endif
|
||||
# #############################################
|
||||
|
||||
RESCOMP = windres
|
||||
INCLUDES += -I../../src/cli -I../../src/module -I../../deps/wren/include -I../../deps/wren/src/vm -I../../deps/wren/src/optional -I../../deps/libuv/include -I../../deps/libuv/src
|
||||
INCLUDES += -I../../src/cli -I../../src/module -I../../deps/wren/include -I../../deps/wren/src/vm -I../../deps/wren/src/optional -I../../deps/libuv/include -I../../deps/libuv/src -I../../deps/cjson -I../../deps/sqlite
|
||||
FORCE_INCLUDE +=
|
||||
ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES)
|
||||
ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES)
|
||||
LIBS += -lpthread -ldl -lm
|
||||
LIBS += -lpthread -ldl -lm -lssl -lcrypto
|
||||
LDDEPS +=
|
||||
LINKCMD = $(CC) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS)
|
||||
define PREBUILDCMDS
|
||||
@ -109,7 +109,15 @@ OBJECTS += $(OBJDIR)/getaddrinfo.o
|
||||
OBJECTS += $(OBJDIR)/getnameinfo.o
|
||||
OBJECTS += $(OBJDIR)/idna.o
|
||||
OBJECTS += $(OBJDIR)/inet.o
|
||||
OBJECTS += $(OBJDIR)/base64.o
|
||||
OBJECTS += $(OBJDIR)/cJSON.o
|
||||
OBJECTS += $(OBJDIR)/crypto.o
|
||||
OBJECTS += $(OBJDIR)/datetime.o
|
||||
OBJECTS += $(OBJDIR)/dns.o
|
||||
OBJECTS += $(OBJDIR)/env.o
|
||||
OBJECTS += $(OBJDIR)/io.o
|
||||
OBJECTS += $(OBJDIR)/json.o
|
||||
OBJECTS += $(OBJDIR)/math_module.o
|
||||
OBJECTS += $(OBJDIR)/linux-core.o
|
||||
OBJECTS += $(OBJDIR)/linux-inotify.o
|
||||
OBJECTS += $(OBJDIR)/linux-syscalls.o
|
||||
@ -129,9 +137,15 @@ OBJECTS += $(OBJDIR)/random-devurandom.o
|
||||
OBJECTS += $(OBJDIR)/random-getrandom.o
|
||||
OBJECTS += $(OBJDIR)/random-sysctl-linux.o
|
||||
OBJECTS += $(OBJDIR)/random.o
|
||||
OBJECTS += $(OBJDIR)/regex.o
|
||||
OBJECTS += $(OBJDIR)/repl.o
|
||||
OBJECTS += $(OBJDIR)/scheduler.o
|
||||
OBJECTS += $(OBJDIR)/signal.o
|
||||
OBJECTS += $(OBJDIR)/signal_module.o
|
||||
OBJECTS += $(OBJDIR)/sqlite3.o
|
||||
OBJECTS += $(OBJDIR)/sqlite_module.o
|
||||
OBJECTS += $(OBJDIR)/subprocess.o
|
||||
OBJECTS += $(OBJDIR)/tls.o
|
||||
OBJECTS += $(OBJDIR)/stream.o
|
||||
OBJECTS += $(OBJDIR)/strscpy.o
|
||||
OBJECTS += $(OBJDIR)/sysinfo-loadavg.o
|
||||
@ -363,9 +377,33 @@ $(OBJDIR)/path.o: ../../src/cli/path.c
|
||||
$(OBJDIR)/vm.o: ../../src/cli/vm.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/base64.o: ../../src/module/base64.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/cJSON.o: ../../deps/cjson/cJSON.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/crypto.o: ../../src/module/crypto.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/datetime.o: ../../src/module/datetime.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/dns.o: ../../src/module/dns.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/env.o: ../../src/module/env.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/io.o: ../../src/module/io.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/json.o: ../../src/module/json.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/math_module.o: ../../src/module/math_module.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/net.o: ../../src/module/net.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
@ -381,6 +419,24 @@ $(OBJDIR)/scheduler.o: ../../src/module/scheduler.c
|
||||
$(OBJDIR)/timer1.o: ../../src/module/timer.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/signal_module.o: ../../src/module/signal_module.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/subprocess.o: ../../src/module/subprocess.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/regex.o: ../../src/module/regex.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/sqlite3.o: ../../deps/sqlite/sqlite3.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/sqlite_module.o: ../../src/module/sqlite_module.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/tls.o: ../../src/module/tls.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
|
||||
-include $(OBJECTS:%.o=%.d)
|
||||
ifneq (,$(PCH))
|
||||
|
||||
@ -3,16 +3,68 @@
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#include "base64.wren.inc"
|
||||
#include "crypto.wren.inc"
|
||||
#include "datetime.wren.inc"
|
||||
#include "dns.wren.inc"
|
||||
#include "env.wren.inc"
|
||||
#include "http.wren.inc"
|
||||
#include "io.wren.inc"
|
||||
#include "jinja.wren.inc"
|
||||
#include "json.wren.inc"
|
||||
#include "math.wren.inc"
|
||||
#include "net.wren.inc"
|
||||
#include "os.wren.inc"
|
||||
#include "regex.wren.inc"
|
||||
#include "repl.wren.inc"
|
||||
#include "scheduler.wren.inc"
|
||||
#include "signal.wren.inc"
|
||||
#include "sqlite.wren.inc"
|
||||
#include "subprocess.wren.inc"
|
||||
#include "timer.wren.inc"
|
||||
#include "tls.wren.inc"
|
||||
#include "websocket.wren.inc"
|
||||
|
||||
extern void base64Encode(WrenVM* vm);
|
||||
extern void base64Decode(WrenVM* vm);
|
||||
extern void cryptoRandomBytes(WrenVM* vm);
|
||||
extern void cryptoMd5(WrenVM* vm);
|
||||
extern void cryptoSha1(WrenVM* vm);
|
||||
extern void cryptoSha256(WrenVM* vm);
|
||||
extern void datetimeNow(WrenVM* vm);
|
||||
extern void datetimeFromTimestamp(WrenVM* vm);
|
||||
extern void datetimeFormat(WrenVM* vm);
|
||||
extern void datetimeComponent(WrenVM* vm);
|
||||
extern void dnsLookup(WrenVM* vm);
|
||||
extern void directoryList(WrenVM* vm);
|
||||
extern void directoryCreate(WrenVM* vm);
|
||||
extern void directoryDelete(WrenVM* vm);
|
||||
extern void envGet(WrenVM* vm);
|
||||
extern void envSet(WrenVM* vm);
|
||||
extern void envDelete(WrenVM* vm);
|
||||
extern void envAll(WrenVM* vm);
|
||||
extern void jsonParse(WrenVM* vm);
|
||||
extern void mathSin(WrenVM* vm);
|
||||
extern void mathCos(WrenVM* vm);
|
||||
extern void mathTan(WrenVM* vm);
|
||||
extern void mathAsin(WrenVM* vm);
|
||||
extern void mathAcos(WrenVM* vm);
|
||||
extern void mathAtan(WrenVM* vm);
|
||||
extern void mathAtan2(WrenVM* vm);
|
||||
extern void mathSinh(WrenVM* vm);
|
||||
extern void mathCosh(WrenVM* vm);
|
||||
extern void mathTanh(WrenVM* vm);
|
||||
extern void mathLog(WrenVM* vm);
|
||||
extern void mathLog10(WrenVM* vm);
|
||||
extern void mathLog2(WrenVM* vm);
|
||||
extern void mathExp(WrenVM* vm);
|
||||
extern void mathPow(WrenVM* vm);
|
||||
extern void mathSqrt(WrenVM* vm);
|
||||
extern void mathCbrt(WrenVM* vm);
|
||||
extern void mathCeil(WrenVM* vm);
|
||||
extern void mathFloor(WrenVM* vm);
|
||||
extern void mathRound(WrenVM* vm);
|
||||
extern void mathAbs(WrenVM* vm);
|
||||
extern void fileAllocate(WrenVM* vm);
|
||||
extern void fileFinalize(void* data);
|
||||
extern void fileDelete(WrenVM* vm);
|
||||
@ -68,6 +120,35 @@ extern void serverFinalize(void* data);
|
||||
extern void serverBind(WrenVM* vm);
|
||||
extern void serverAccept(WrenVM* vm);
|
||||
extern void serverClose(WrenVM* vm);
|
||||
extern void signalTrap(WrenVM* vm);
|
||||
extern void signalIgnore(WrenVM* vm);
|
||||
extern void signalReset(WrenVM* vm);
|
||||
extern void sqliteAllocate(WrenVM* vm);
|
||||
extern void sqliteFinalize(void* data);
|
||||
extern void sqliteExecute(WrenVM* vm);
|
||||
extern void sqliteExecuteParams(WrenVM* vm);
|
||||
extern void sqliteQuery(WrenVM* vm);
|
||||
extern void sqliteQueryParams(WrenVM* vm);
|
||||
extern void sqliteClose(WrenVM* vm);
|
||||
extern void sqliteLastInsertId(WrenVM* vm);
|
||||
extern void sqliteChanges(WrenVM* vm);
|
||||
extern void subprocessRun(WrenVM* vm);
|
||||
extern void regexAllocate(WrenVM* vm);
|
||||
extern void regexFinalize(void* data);
|
||||
extern void regexTest(WrenVM* vm);
|
||||
extern void regexMatch(WrenVM* vm);
|
||||
extern void regexMatchAll(WrenVM* vm);
|
||||
extern void regexReplace(WrenVM* vm);
|
||||
extern void regexReplaceAll(WrenVM* vm);
|
||||
extern void regexSplit(WrenVM* vm);
|
||||
extern void regexPattern(WrenVM* vm);
|
||||
extern void regexFlags(WrenVM* vm);
|
||||
extern void tlsSocketAllocate(WrenVM* vm);
|
||||
extern void tlsSocketFinalize(void* data);
|
||||
extern void tlsSocketConnect(WrenVM* vm);
|
||||
extern void tlsSocketWrite(WrenVM* vm);
|
||||
extern void tlsSocketRead(WrenVM* vm);
|
||||
extern void tlsSocketClose(WrenVM* vm);
|
||||
|
||||
// The maximum number of foreign methods a single class defines. Ideally, we
|
||||
// would use variable-length arrays for each class in the table below, but
|
||||
@ -77,7 +158,7 @@ extern void serverClose(WrenVM* vm);
|
||||
// If you add a new method to the longest class below, make sure to bump this.
|
||||
// Note that it also includes an extra slot for the sentinel value indicating
|
||||
// the end of the list.
|
||||
#define MAX_METHODS_PER_CLASS 14
|
||||
#define MAX_METHODS_PER_CLASS 24
|
||||
|
||||
// The maximum number of foreign classes a single built-in module defines.
|
||||
//
|
||||
@ -137,6 +218,45 @@ typedef struct
|
||||
// The array of built-in modules.
|
||||
static ModuleRegistry modules[] =
|
||||
{
|
||||
MODULE(base64)
|
||||
CLASS(Base64)
|
||||
STATIC_METHOD("encode(_)", base64Encode)
|
||||
STATIC_METHOD("decode(_)", base64Decode)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(crypto)
|
||||
CLASS(Crypto)
|
||||
STATIC_METHOD("randomBytes_(_,_)", cryptoRandomBytes)
|
||||
END_CLASS
|
||||
CLASS(Hash)
|
||||
STATIC_METHOD("md5_(_)", cryptoMd5)
|
||||
STATIC_METHOD("sha1_(_)", cryptoSha1)
|
||||
STATIC_METHOD("sha256_(_)", cryptoSha256)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(datetime)
|
||||
CLASS(DateTime)
|
||||
STATIC_METHOD("now_()", datetimeNow)
|
||||
STATIC_METHOD("fromTimestamp_(_)", datetimeFromTimestamp)
|
||||
STATIC_METHOD("format_(_,_)", datetimeFormat)
|
||||
STATIC_METHOD("component_(_,_)", datetimeComponent)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(dns)
|
||||
CLASS(Dns)
|
||||
STATIC_METHOD("lookup_(_,_,_)", dnsLookup)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(env)
|
||||
CLASS(Environment)
|
||||
STATIC_METHOD("get(_)", envGet)
|
||||
STATIC_METHOD("set(_,_)", envSet)
|
||||
STATIC_METHOD("delete(_)", envDelete)
|
||||
STATIC_METHOD("all", envAll)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(http)
|
||||
END_MODULE
|
||||
MODULE(io)
|
||||
CLASS(Directory)
|
||||
STATIC_METHOD("create_(_,_)", directoryCreate)
|
||||
@ -186,6 +306,38 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("write(_)", stderrWrite)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(jinja)
|
||||
END_MODULE
|
||||
MODULE(json)
|
||||
CLASS(Json)
|
||||
STATIC_METHOD("parse(_)", jsonParse)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(math)
|
||||
CLASS(Math)
|
||||
STATIC_METHOD("sin(_)", mathSin)
|
||||
STATIC_METHOD("cos(_)", mathCos)
|
||||
STATIC_METHOD("tan(_)", mathTan)
|
||||
STATIC_METHOD("asin(_)", mathAsin)
|
||||
STATIC_METHOD("acos(_)", mathAcos)
|
||||
STATIC_METHOD("atan(_)", mathAtan)
|
||||
STATIC_METHOD("atan2(_,_)", mathAtan2)
|
||||
STATIC_METHOD("sinh(_)", mathSinh)
|
||||
STATIC_METHOD("cosh(_)", mathCosh)
|
||||
STATIC_METHOD("tanh(_)", mathTanh)
|
||||
STATIC_METHOD("log(_)", mathLog)
|
||||
STATIC_METHOD("log10(_)", mathLog10)
|
||||
STATIC_METHOD("log2(_)", mathLog2)
|
||||
STATIC_METHOD("exp(_)", mathExp)
|
||||
STATIC_METHOD("pow(_,_)", mathPow)
|
||||
STATIC_METHOD("sqrt(_)", mathSqrt)
|
||||
STATIC_METHOD("cbrt(_)", mathCbrt)
|
||||
STATIC_METHOD("ceil(_)", mathCeil)
|
||||
STATIC_METHOD("floor(_)", mathFloor)
|
||||
STATIC_METHOD("round(_)", mathRound)
|
||||
STATIC_METHOD("abs(_)", mathAbs)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(net)
|
||||
CLASS(Socket)
|
||||
ALLOCATE(socketAllocate)
|
||||
@ -218,6 +370,20 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("version", processVersion)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(regex)
|
||||
CLASS(Regex)
|
||||
ALLOCATE(regexAllocate)
|
||||
FINALIZE(regexFinalize)
|
||||
METHOD("test(_)", regexTest)
|
||||
METHOD("match(_)", regexMatch)
|
||||
METHOD("matchAll(_)", regexMatchAll)
|
||||
METHOD("replace(_,_)", regexReplace)
|
||||
METHOD("replaceAll(_,_)", regexReplaceAll)
|
||||
METHOD("split(_)", regexSplit)
|
||||
METHOD("pattern", regexPattern)
|
||||
METHOD("flags", regexFlags)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(repl)
|
||||
END_MODULE
|
||||
MODULE(scheduler)
|
||||
@ -225,11 +391,48 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("captureMethods_()", schedulerCaptureMethods)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(signal)
|
||||
CLASS(Signal)
|
||||
STATIC_METHOD("trap_(_,_)", signalTrap)
|
||||
STATIC_METHOD("ignore_(_)", signalIgnore)
|
||||
STATIC_METHOD("reset_(_)", signalReset)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(sqlite)
|
||||
CLASS(Database)
|
||||
ALLOCATE(sqliteAllocate)
|
||||
FINALIZE(sqliteFinalize)
|
||||
METHOD("execute(_)", sqliteExecute)
|
||||
METHOD("execute(_,_)", sqliteExecuteParams)
|
||||
METHOD("query(_)", sqliteQuery)
|
||||
METHOD("query(_,_)", sqliteQueryParams)
|
||||
METHOD("close()", sqliteClose)
|
||||
METHOD("lastInsertId", sqliteLastInsertId)
|
||||
METHOD("changes", sqliteChanges)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(subprocess)
|
||||
CLASS(Subprocess)
|
||||
STATIC_METHOD("run_(_,_)", subprocessRun)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(timer)
|
||||
CLASS(Timer)
|
||||
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(tls)
|
||||
CLASS(TlsSocket)
|
||||
ALLOCATE(tlsSocketAllocate)
|
||||
FINALIZE(tlsSocketFinalize)
|
||||
METHOD("connect_(_,_,_,_)", tlsSocketConnect)
|
||||
METHOD("write_(_,_)", tlsSocketWrite)
|
||||
METHOD("read_(_)", tlsSocketRead)
|
||||
METHOD("close_()", tlsSocketClose)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(websocket)
|
||||
END_MODULE
|
||||
|
||||
SENTINEL_MODULE
|
||||
};
|
||||
|
||||
163
src/module/base64.c
Normal file
163
src/module/base64.c
Normal file
@ -0,0 +1,163 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "base64.h"
|
||||
#include "wren.h"
|
||||
|
||||
static const char base64_chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static const unsigned char base64_lookup[256] = {
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
||||
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||
};
|
||||
|
||||
void base64Encode(WrenVM* vm) {
|
||||
int length = 0;
|
||||
const char* data = wrenGetSlotBytes(vm, 1, &length);
|
||||
|
||||
if (data == NULL || length == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t outputLen = 4 * ((length + 2) / 3);
|
||||
char* output = (char*)malloc(outputLen + 1);
|
||||
if (output == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
unsigned char a3[3];
|
||||
unsigned char a4[4];
|
||||
|
||||
while (length--) {
|
||||
a3[i++] = (unsigned char)*(data++);
|
||||
if (i == 3) {
|
||||
a4[0] = (a3[0] & 0xfc) >> 2;
|
||||
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
|
||||
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
|
||||
a4[3] = a3[2] & 0x3f;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
output[j++] = base64_chars[a4[i]];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
for (size_t k = i; k < 3; k++) {
|
||||
a3[k] = '\0';
|
||||
}
|
||||
|
||||
a4[0] = (a3[0] & 0xfc) >> 2;
|
||||
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
|
||||
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
|
||||
|
||||
for (size_t k = 0; k < i + 1; k++) {
|
||||
output[j++] = base64_chars[a4[k]];
|
||||
}
|
||||
|
||||
while (i++ < 3) {
|
||||
output[j++] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
output[j] = '\0';
|
||||
wrenSetSlotString(vm, 0, output);
|
||||
free(output);
|
||||
}
|
||||
|
||||
void base64Decode(WrenVM* vm) {
|
||||
const char* data = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (data == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t inputLen = strlen(data);
|
||||
if (inputLen == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t padding = 0;
|
||||
if (inputLen >= 1 && data[inputLen - 1] == '=') padding++;
|
||||
if (inputLen >= 2 && data[inputLen - 2] == '=') padding++;
|
||||
|
||||
size_t outputLen = (inputLen / 4) * 3 - padding;
|
||||
char* output = (char*)malloc(outputLen + 1);
|
||||
if (output == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
unsigned char a4[4];
|
||||
unsigned char a3[3];
|
||||
|
||||
while (inputLen--) {
|
||||
unsigned char c = (unsigned char)*data++;
|
||||
if (c == '=') break;
|
||||
if (base64_lookup[c] == 64) continue;
|
||||
|
||||
a4[i++] = c;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
a4[i] = base64_lookup[a4[i]];
|
||||
}
|
||||
|
||||
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
|
||||
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
|
||||
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
output[j++] = (char)a3[i];
|
||||
}
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
for (size_t k = i; k < 4; k++) {
|
||||
a4[k] = 0;
|
||||
}
|
||||
for (size_t k = 0; k < 4; k++) {
|
||||
a4[k] = base64_lookup[a4[k]];
|
||||
}
|
||||
|
||||
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
|
||||
a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2);
|
||||
a3[2] = ((a4[2] & 0x03) << 6) + a4[3];
|
||||
|
||||
for (size_t k = 0; k < i - 1; k++) {
|
||||
output[j++] = (char)a3[k];
|
||||
}
|
||||
}
|
||||
|
||||
output[j] = '\0';
|
||||
wrenSetSlotBytes(vm, 0, output, j);
|
||||
free(output);
|
||||
}
|
||||
11
src/module/base64.h
Normal file
11
src/module/base64.h
Normal file
@ -0,0 +1,11 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_base64_h
|
||||
#define wren_base64_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void base64Encode(WrenVM* vm);
|
||||
void base64Decode(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
28
src/module/base64.wren
vendored
Normal file
28
src/module/base64.wren
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Base64 {
|
||||
foreign static encode(data)
|
||||
foreign static decode(data)
|
||||
|
||||
static encodeUrl(data) {
|
||||
var result = encode(data)
|
||||
result = result.replace("+", "-")
|
||||
result = result.replace("/", "_")
|
||||
while (result.endsWith("=")) {
|
||||
result = result[0...-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static decodeUrl(data) {
|
||||
var result = data.replace("-", "+")
|
||||
result = result.replace("_", "/")
|
||||
var padding = 4 - result.count % 4
|
||||
if (padding < 4) {
|
||||
for (i in 0...padding) {
|
||||
result = result + "="
|
||||
}
|
||||
}
|
||||
return decode(result)
|
||||
}
|
||||
}
|
||||
32
src/module/base64.wren.inc
Normal file
32
src/module/base64.wren.inc
Normal file
@ -0,0 +1,32 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/base64.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* base64ModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Base64 {\n"
|
||||
" foreign static encode(data)\n"
|
||||
" foreign static decode(data)\n"
|
||||
"\n"
|
||||
" static encodeUrl(data) {\n"
|
||||
" var result = encode(data)\n"
|
||||
" result = result.replace(\"+\", \"-\")\n"
|
||||
" result = result.replace(\"/\", \"_\")\n"
|
||||
" while (result.endsWith(\"=\")) {\n"
|
||||
" result = result[0...-1]\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeUrl(data) {\n"
|
||||
" var result = data.replace(\"-\", \"+\")\n"
|
||||
" result = result.replace(\"_\", \"/\")\n"
|
||||
" var padding = 4 - result.count % 4\n"
|
||||
" if (padding < 4) {\n"
|
||||
" for (i in 0...padding) {\n"
|
||||
" result = result + \"=\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return decode(result)\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
373
src/module/crypto.c
Normal file
373
src/module/crypto.c
Normal file
@ -0,0 +1,373 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "crypto.h"
|
||||
#include "scheduler.h"
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
#include "uv.h"
|
||||
|
||||
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||
#define ROTR32(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
|
||||
|
||||
static void md5Transform(uint32_t state[4], const uint8_t block[64]) {
|
||||
static const uint32_t k[64] = {
|
||||
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
|
||||
};
|
||||
static const uint8_t r[64] = {
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
|
||||
};
|
||||
|
||||
uint32_t m[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
m[i] = block[i * 4] | (block[i * 4 + 1] << 8) |
|
||||
(block[i * 4 + 2] << 16) | (block[i * 4 + 3] << 24);
|
||||
}
|
||||
|
||||
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
uint32_t f, g;
|
||||
if (i < 16) {
|
||||
f = (b & c) | (~b & d);
|
||||
g = i;
|
||||
} else if (i < 32) {
|
||||
f = (d & b) | (~d & c);
|
||||
g = (5 * i + 1) % 16;
|
||||
} else if (i < 48) {
|
||||
f = b ^ c ^ d;
|
||||
g = (3 * i + 5) % 16;
|
||||
} else {
|
||||
f = c ^ (b | ~d);
|
||||
g = (7 * i) % 16;
|
||||
}
|
||||
uint32_t temp = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b = b + ROTL32(a + f + k[i] + m[g], r[i]);
|
||||
a = temp;
|
||||
}
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
}
|
||||
|
||||
static void md5(const uint8_t* data, size_t len, uint8_t digest[16]) {
|
||||
uint32_t state[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
|
||||
uint8_t block[64];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + 64 <= len; i += 64) {
|
||||
md5Transform(state, data + i);
|
||||
}
|
||||
|
||||
size_t remaining = len - i;
|
||||
memcpy(block, data + i, remaining);
|
||||
block[remaining++] = 0x80;
|
||||
|
||||
if (remaining > 56) {
|
||||
memset(block + remaining, 0, 64 - remaining);
|
||||
md5Transform(state, block);
|
||||
remaining = 0;
|
||||
}
|
||||
memset(block + remaining, 0, 56 - remaining);
|
||||
|
||||
uint64_t bits = len * 8;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
block[56 + j] = (bits >> (j * 8)) & 0xFF;
|
||||
}
|
||||
md5Transform(state, block);
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
digest[j * 4] = state[j] & 0xFF;
|
||||
digest[j * 4 + 1] = (state[j] >> 8) & 0xFF;
|
||||
digest[j * 4 + 2] = (state[j] >> 16) & 0xFF;
|
||||
digest[j * 4 + 3] = (state[j] >> 24) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void sha1Transform(uint32_t state[5], const uint8_t block[64]) {
|
||||
uint32_t w[80];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
|
||||
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
|
||||
}
|
||||
for (int i = 16; i < 80; i++) {
|
||||
w[i] = ROTL32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
|
||||
}
|
||||
|
||||
uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4];
|
||||
|
||||
for (int i = 0; i < 80; i++) {
|
||||
uint32_t f, k;
|
||||
if (i < 20) {
|
||||
f = (b & c) | (~b & d);
|
||||
k = 0x5A827999;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1;
|
||||
} else if (i < 60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDC;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6;
|
||||
}
|
||||
uint32_t temp = ROTL32(a, 5) + f + e + k + w[i];
|
||||
e = d;
|
||||
d = c;
|
||||
c = ROTL32(b, 30);
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
}
|
||||
|
||||
static void sha1(const uint8_t* data, size_t len, uint8_t digest[20]) {
|
||||
uint32_t state[5] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
|
||||
uint8_t block[64];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + 64 <= len; i += 64) {
|
||||
sha1Transform(state, data + i);
|
||||
}
|
||||
|
||||
size_t remaining = len - i;
|
||||
memcpy(block, data + i, remaining);
|
||||
block[remaining++] = 0x80;
|
||||
|
||||
if (remaining > 56) {
|
||||
memset(block + remaining, 0, 64 - remaining);
|
||||
sha1Transform(state, block);
|
||||
remaining = 0;
|
||||
}
|
||||
memset(block + remaining, 0, 56 - remaining);
|
||||
|
||||
uint64_t bits = len * 8;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
|
||||
}
|
||||
sha1Transform(state, block);
|
||||
|
||||
for (int j = 0; j < 5; j++) {
|
||||
digest[j * 4] = (state[j] >> 24) & 0xFF;
|
||||
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
|
||||
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
|
||||
digest[j * 4 + 3] = state[j] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256Transform(uint32_t state[8], const uint8_t block[64]) {
|
||||
static const uint32_t k[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
uint32_t w[64];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
w[i] = (block[i * 4] << 24) | (block[i * 4 + 1] << 16) |
|
||||
(block[i * 4 + 2] << 8) | block[i * 4 + 3];
|
||||
}
|
||||
for (int i = 16; i < 64; i++) {
|
||||
uint32_t s0 = ROTR32(w[i-15], 7) ^ ROTR32(w[i-15], 18) ^ (w[i-15] >> 3);
|
||||
uint32_t s1 = ROTR32(w[i-2], 17) ^ ROTR32(w[i-2], 19) ^ (w[i-2] >> 10);
|
||||
w[i] = w[i-16] + s0 + w[i-7] + s1;
|
||||
}
|
||||
|
||||
uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
|
||||
uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
uint32_t S1 = ROTR32(e, 6) ^ ROTR32(e, 11) ^ ROTR32(e, 25);
|
||||
uint32_t ch = (e & f) ^ (~e & g);
|
||||
uint32_t temp1 = h + S1 + ch + k[i] + w[i];
|
||||
uint32_t S0 = ROTR32(a, 2) ^ ROTR32(a, 13) ^ ROTR32(a, 22);
|
||||
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
uint32_t temp2 = S0 + maj;
|
||||
|
||||
h = g; g = f; f = e; e = d + temp1;
|
||||
d = c; c = b; b = a; a = temp1 + temp2;
|
||||
}
|
||||
|
||||
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
|
||||
state[4] += e; state[5] += f; state[6] += g; state[7] += h;
|
||||
}
|
||||
|
||||
static void sha256(const uint8_t* data, size_t len, uint8_t digest[32]) {
|
||||
uint32_t state[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
};
|
||||
uint8_t block[64];
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + 64 <= len; i += 64) {
|
||||
sha256Transform(state, data + i);
|
||||
}
|
||||
|
||||
size_t remaining = len - i;
|
||||
memcpy(block, data + i, remaining);
|
||||
block[remaining++] = 0x80;
|
||||
|
||||
if (remaining > 56) {
|
||||
memset(block + remaining, 0, 64 - remaining);
|
||||
sha256Transform(state, block);
|
||||
remaining = 0;
|
||||
}
|
||||
memset(block + remaining, 0, 56 - remaining);
|
||||
|
||||
uint64_t bits = len * 8;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
block[56 + j] = (bits >> ((7 - j) * 8)) & 0xFF;
|
||||
}
|
||||
sha256Transform(state, block);
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
digest[j * 4] = (state[j] >> 24) & 0xFF;
|
||||
digest[j * 4 + 1] = (state[j] >> 16) & 0xFF;
|
||||
digest[j * 4 + 2] = (state[j] >> 8) & 0xFF;
|
||||
digest[j * 4 + 3] = state[j] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void randomCallback(uv_random_t* req, int status, void* buf, size_t len) {
|
||||
WrenHandle* fiber = (WrenHandle*)req->data;
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
if (status == 0) {
|
||||
uint8_t* bytes = (uint8_t*)buf;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
wrenSetSlotDouble(vm, 1, (double)bytes[i]);
|
||||
wrenInsertInList(vm, 2, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
free(req);
|
||||
schedulerResume(fiber, true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void cryptoRandomBytes(WrenVM* vm) {
|
||||
int length = (int)wrenGetSlotDouble(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
if (length < 0 || length > 65536) {
|
||||
wrenSetSlotString(vm, 0, "Length must be between 0 and 65536.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_random_t* req = (uv_random_t*)malloc(sizeof(uv_random_t));
|
||||
void* buf = malloc(length);
|
||||
|
||||
if (req == NULL || buf == NULL) {
|
||||
if (req) free(req);
|
||||
if (buf) free(buf);
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
req->data = fiber;
|
||||
|
||||
int result = uv_random(getLoop(), req, buf, length, 0, randomCallback);
|
||||
if (result != 0) {
|
||||
free(req);
|
||||
free(buf);
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, uv_strerror(result));
|
||||
wrenAbortFiber(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void hashHelper(WrenVM* vm, void (*hashFn)(const uint8_t*, size_t, uint8_t*), int digestLen) {
|
||||
int count = wrenGetListCount(vm, 1);
|
||||
uint8_t* data = (uint8_t*)malloc(count);
|
||||
if (data == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
for (int i = 0; i < count; i++) {
|
||||
wrenGetListElement(vm, 1, i, 2);
|
||||
data[i] = (uint8_t)wrenGetSlotDouble(vm, 2);
|
||||
}
|
||||
|
||||
uint8_t* digest = (uint8_t*)malloc(digestLen);
|
||||
if (digest == NULL) {
|
||||
free(data);
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
hashFn(data, count, digest);
|
||||
free(data);
|
||||
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
for (int i = 0; i < digestLen; i++) {
|
||||
wrenSetSlotDouble(vm, 2, (double)digest[i]);
|
||||
wrenInsertInList(vm, 0, -1, 2);
|
||||
}
|
||||
free(digest);
|
||||
}
|
||||
|
||||
void cryptoMd5(WrenVM* vm) {
|
||||
hashHelper(vm, md5, 16);
|
||||
}
|
||||
|
||||
void cryptoSha1(WrenVM* vm) {
|
||||
hashHelper(vm, sha1, 20);
|
||||
}
|
||||
|
||||
void cryptoSha256(WrenVM* vm) {
|
||||
hashHelper(vm, sha256, 32);
|
||||
}
|
||||
13
src/module/crypto.h
Normal file
13
src/module/crypto.h
Normal file
@ -0,0 +1,13 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_crypto_h
|
||||
#define wren_crypto_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void cryptoRandomBytes(WrenVM* vm);
|
||||
void cryptoMd5(WrenVM* vm);
|
||||
void cryptoSha1(WrenVM* vm);
|
||||
void cryptoSha256(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
63
src/module/crypto.wren
vendored
Normal file
63
src/module/crypto.wren
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class Crypto {
|
||||
foreign static randomBytes_(length, fiber)
|
||||
|
||||
static randomBytes(length) {
|
||||
if (!(length is Num)) Fiber.abort("Length must be a number.")
|
||||
if (length < 0) Fiber.abort("Length must be non-negative.")
|
||||
randomBytes_(length, Fiber.current)
|
||||
return Scheduler.runNextScheduled_()
|
||||
}
|
||||
|
||||
static randomInt(min, max) {
|
||||
if (!(min is Num) || !(max is Num)) Fiber.abort("Arguments must be numbers.")
|
||||
if (min >= max) Fiber.abort("Min must be less than max.")
|
||||
var range = max - min
|
||||
var bytes = randomBytes(4)
|
||||
var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
|
||||
if (value < 0) value = -value
|
||||
return min + (value % range)
|
||||
}
|
||||
}
|
||||
|
||||
class Hash {
|
||||
foreign static md5_(data)
|
||||
foreign static sha1_(data)
|
||||
foreign static sha256_(data)
|
||||
|
||||
static md5(data) {
|
||||
if (data is String) data = data.bytes.toList
|
||||
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
|
||||
return md5_(data)
|
||||
}
|
||||
|
||||
static sha1(data) {
|
||||
if (data is String) data = data.bytes.toList
|
||||
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
|
||||
return sha1_(data)
|
||||
}
|
||||
|
||||
static sha256(data) {
|
||||
if (data is String) data = data.bytes.toList
|
||||
if (!(data is List)) Fiber.abort("Data must be a String or List of bytes.")
|
||||
return sha256_(data)
|
||||
}
|
||||
|
||||
static toHex(bytes) {
|
||||
var hex = ""
|
||||
for (b in bytes) {
|
||||
var hi = (b >> 4) & 0x0F
|
||||
var lo = b & 0x0F
|
||||
hex = hex + hexDigit_(hi) + hexDigit_(lo)
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(97 + n - 10)
|
||||
}
|
||||
}
|
||||
67
src/module/crypto.wren.inc
Normal file
67
src/module/crypto.wren.inc
Normal file
@ -0,0 +1,67 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/crypto.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* cryptoModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class Crypto {\n"
|
||||
" foreign static randomBytes_(length, fiber)\n"
|
||||
"\n"
|
||||
" static randomBytes(length) {\n"
|
||||
" if (!(length is Num)) Fiber.abort(\"Length must be a number.\")\n"
|
||||
" if (length < 0) Fiber.abort(\"Length must be non-negative.\")\n"
|
||||
" randomBytes_(length, Fiber.current)\n"
|
||||
" return Scheduler.runNextScheduled_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static randomInt(min, max) {\n"
|
||||
" if (!(min is Num) || !(max is Num)) Fiber.abort(\"Arguments must be numbers.\")\n"
|
||||
" if (min >= max) Fiber.abort(\"Min must be less than max.\")\n"
|
||||
" var range = max - min\n"
|
||||
" var bytes = randomBytes(4)\n"
|
||||
" var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]\n"
|
||||
" if (value < 0) value = -value\n"
|
||||
" return min + (value % range)\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Hash {\n"
|
||||
" foreign static md5_(data)\n"
|
||||
" foreign static sha1_(data)\n"
|
||||
" foreign static sha256_(data)\n"
|
||||
"\n"
|
||||
" static md5(data) {\n"
|
||||
" if (data is String) data = data.bytes.toList\n"
|
||||
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
|
||||
" return md5_(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static sha1(data) {\n"
|
||||
" if (data is String) data = data.bytes.toList\n"
|
||||
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
|
||||
" return sha1_(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static sha256(data) {\n"
|
||||
" if (data is String) data = data.bytes.toList\n"
|
||||
" if (!(data is List)) Fiber.abort(\"Data must be a String or List of bytes.\")\n"
|
||||
" return sha256_(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toHex(bytes) {\n"
|
||||
" var hex = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" var hi = (b >> 4) & 0x0F\n"
|
||||
" var lo = b & 0x0F\n"
|
||||
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
|
||||
" }\n"
|
||||
" return hex\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(97 + n - 10)\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
74
src/module/datetime.c
Normal file
74
src/module/datetime.c
Normal file
@ -0,0 +1,74 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "datetime.h"
|
||||
#include "wren.h"
|
||||
|
||||
void datetimeNow(WrenVM* vm) {
|
||||
time_t now = time(NULL);
|
||||
wrenSetSlotDouble(vm, 0, (double)now);
|
||||
}
|
||||
|
||||
void datetimeFromTimestamp(WrenVM* vm) {
|
||||
double timestamp = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, timestamp);
|
||||
}
|
||||
|
||||
void datetimeFormat(WrenVM* vm) {
|
||||
double timestamp = wrenGetSlotDouble(vm, 1);
|
||||
const char* pattern = wrenGetSlotString(vm, 2);
|
||||
|
||||
if (pattern == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
time_t t = (time_t)timestamp;
|
||||
struct tm* tm_info = localtime(&t);
|
||||
|
||||
if (tm_info == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
size_t result = strftime(buffer, sizeof(buffer), pattern, tm_info);
|
||||
|
||||
if (result == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotString(vm, 0, buffer);
|
||||
}
|
||||
|
||||
void datetimeComponent(WrenVM* vm) {
|
||||
double timestamp = wrenGetSlotDouble(vm, 1);
|
||||
int index = (int)wrenGetSlotDouble(vm, 2);
|
||||
|
||||
time_t t = (time_t)timestamp;
|
||||
struct tm* tm_info = localtime(&t);
|
||||
|
||||
if (tm_info == NULL) {
|
||||
wrenSetSlotDouble(vm, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
switch (index) {
|
||||
case 0: result = tm_info->tm_year + 1900; break;
|
||||
case 1: result = tm_info->tm_mon + 1; break;
|
||||
case 2: result = tm_info->tm_mday; break;
|
||||
case 3: result = tm_info->tm_hour; break;
|
||||
case 4: result = tm_info->tm_min; break;
|
||||
case 5: result = tm_info->tm_sec; break;
|
||||
case 6: result = tm_info->tm_wday; break;
|
||||
case 7: result = tm_info->tm_yday + 1; break;
|
||||
case 8: result = tm_info->tm_isdst; break;
|
||||
default: result = 0; break;
|
||||
}
|
||||
|
||||
wrenSetSlotDouble(vm, 0, (double)result);
|
||||
}
|
||||
13
src/module/datetime.h
Normal file
13
src/module/datetime.h
Normal file
@ -0,0 +1,13 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_datetime_h
|
||||
#define wren_datetime_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void datetimeNow(WrenVM* vm);
|
||||
void datetimeFromTimestamp(WrenVM* vm);
|
||||
void datetimeFormat(WrenVM* vm);
|
||||
void datetimeComponent(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
113
src/module/datetime.wren
vendored
Normal file
113
src/module/datetime.wren
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class DateTime {
|
||||
foreign static now_()
|
||||
foreign static fromTimestamp_(timestamp)
|
||||
foreign static format_(timestamp, pattern)
|
||||
|
||||
construct now() {
|
||||
_timestamp = DateTime.now_()
|
||||
}
|
||||
|
||||
construct fromTimestamp(timestamp) {
|
||||
_timestamp = timestamp
|
||||
}
|
||||
|
||||
static parse(string, format) {
|
||||
Fiber.abort("DateTime.parse not implemented yet")
|
||||
}
|
||||
|
||||
timestamp { _timestamp }
|
||||
|
||||
year { DateTime.component_(_timestamp, 0) }
|
||||
month { DateTime.component_(_timestamp, 1) }
|
||||
day { DateTime.component_(_timestamp, 2) }
|
||||
hour { DateTime.component_(_timestamp, 3) }
|
||||
minute { DateTime.component_(_timestamp, 4) }
|
||||
second { DateTime.component_(_timestamp, 5) }
|
||||
dayOfWeek { DateTime.component_(_timestamp, 6) }
|
||||
dayOfYear { DateTime.component_(_timestamp, 7) }
|
||||
isDst { DateTime.component_(_timestamp, 8) == 1 }
|
||||
|
||||
foreign static component_(timestamp, index)
|
||||
|
||||
format(pattern) { DateTime.format_(_timestamp, pattern) }
|
||||
|
||||
toIso8601 { format("\%Y-\%m-\%dT\%H:\%M:\%S") }
|
||||
|
||||
toString { toIso8601 }
|
||||
|
||||
+(duration) {
|
||||
if (!(duration is Duration)) Fiber.abort("Expected Duration.")
|
||||
return DateTime.fromTimestamp(_timestamp + duration.seconds)
|
||||
}
|
||||
|
||||
-(other) {
|
||||
if (other is DateTime) {
|
||||
return Duration.fromSeconds(_timestamp - other.timestamp)
|
||||
}
|
||||
if (other is Duration) {
|
||||
return DateTime.fromTimestamp(_timestamp - other.seconds)
|
||||
}
|
||||
Fiber.abort("Expected DateTime or Duration.")
|
||||
}
|
||||
|
||||
==(other) {
|
||||
if (!(other is DateTime)) return false
|
||||
return _timestamp == other.timestamp
|
||||
}
|
||||
|
||||
<(other) { _timestamp < other.timestamp }
|
||||
>(other) { _timestamp > other.timestamp }
|
||||
<=(other) { _timestamp <= other.timestamp }
|
||||
>=(other) { _timestamp >= other.timestamp }
|
||||
}
|
||||
|
||||
class Duration {
|
||||
construct fromMilliseconds(ms) {
|
||||
_ms = ms
|
||||
}
|
||||
|
||||
construct fromSeconds(s) {
|
||||
_ms = s * 1000
|
||||
}
|
||||
|
||||
construct fromMinutes(m) {
|
||||
_ms = m * 60 * 1000
|
||||
}
|
||||
|
||||
construct fromHours(h) {
|
||||
_ms = h * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
construct fromDays(d) {
|
||||
_ms = d * 24 * 60 * 60 * 1000
|
||||
}
|
||||
|
||||
milliseconds { _ms }
|
||||
seconds { _ms / 1000 }
|
||||
minutes { _ms / 60000 }
|
||||
hours { _ms / 3600000 }
|
||||
days { _ms / 86400000 }
|
||||
|
||||
+(other) {
|
||||
if (!(other is Duration)) Fiber.abort("Expected Duration.")
|
||||
return Duration.fromMilliseconds(_ms + other.milliseconds)
|
||||
}
|
||||
|
||||
-(other) {
|
||||
if (!(other is Duration)) Fiber.abort("Expected Duration.")
|
||||
return Duration.fromMilliseconds(_ms - other.milliseconds)
|
||||
}
|
||||
|
||||
*(factor) {
|
||||
return Duration.fromMilliseconds(_ms * factor)
|
||||
}
|
||||
|
||||
==(other) {
|
||||
if (!(other is Duration)) return false
|
||||
return _ms == other.milliseconds
|
||||
}
|
||||
|
||||
toString { "%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s" }
|
||||
}
|
||||
117
src/module/datetime.wren.inc
Normal file
117
src/module/datetime.wren.inc
Normal file
@ -0,0 +1,117 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/datetime.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* datetimeModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class DateTime {\n"
|
||||
" foreign static now_()\n"
|
||||
" foreign static fromTimestamp_(timestamp)\n"
|
||||
" foreign static format_(timestamp, pattern)\n"
|
||||
"\n"
|
||||
" construct now() {\n"
|
||||
" _timestamp = DateTime.now_()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct fromTimestamp(timestamp) {\n"
|
||||
" _timestamp = timestamp\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parse(string, format) {\n"
|
||||
" Fiber.abort(\"DateTime.parse not implemented yet\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" timestamp { _timestamp }\n"
|
||||
"\n"
|
||||
" year { DateTime.component_(_timestamp, 0) }\n"
|
||||
" month { DateTime.component_(_timestamp, 1) }\n"
|
||||
" day { DateTime.component_(_timestamp, 2) }\n"
|
||||
" hour { DateTime.component_(_timestamp, 3) }\n"
|
||||
" minute { DateTime.component_(_timestamp, 4) }\n"
|
||||
" second { DateTime.component_(_timestamp, 5) }\n"
|
||||
" dayOfWeek { DateTime.component_(_timestamp, 6) }\n"
|
||||
" dayOfYear { DateTime.component_(_timestamp, 7) }\n"
|
||||
" isDst { DateTime.component_(_timestamp, 8) == 1 }\n"
|
||||
"\n"
|
||||
" foreign static component_(timestamp, index)\n"
|
||||
"\n"
|
||||
" format(pattern) { DateTime.format_(_timestamp, pattern) }\n"
|
||||
"\n"
|
||||
" toIso8601 { format(\"\\%Y-\\%m-\\%dT\\%H:\\%M:\\%S\") }\n"
|
||||
"\n"
|
||||
" toString { toIso8601 }\n"
|
||||
"\n"
|
||||
" +(duration) {\n"
|
||||
" if (!(duration is Duration)) Fiber.abort(\"Expected Duration.\")\n"
|
||||
" return DateTime.fromTimestamp(_timestamp + duration.seconds)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" -(other) {\n"
|
||||
" if (other is DateTime) {\n"
|
||||
" return Duration.fromSeconds(_timestamp - other.timestamp)\n"
|
||||
" }\n"
|
||||
" if (other is Duration) {\n"
|
||||
" return DateTime.fromTimestamp(_timestamp - other.seconds)\n"
|
||||
" }\n"
|
||||
" Fiber.abort(\"Expected DateTime or Duration.\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ==(other) {\n"
|
||||
" if (!(other is DateTime)) return false\n"
|
||||
" return _timestamp == other.timestamp\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" <(other) { _timestamp < other.timestamp }\n"
|
||||
" >(other) { _timestamp > other.timestamp }\n"
|
||||
" <=(other) { _timestamp <= other.timestamp }\n"
|
||||
" >=(other) { _timestamp >= other.timestamp }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Duration {\n"
|
||||
" construct fromMilliseconds(ms) {\n"
|
||||
" _ms = ms\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct fromSeconds(s) {\n"
|
||||
" _ms = s * 1000\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct fromMinutes(m) {\n"
|
||||
" _ms = m * 60 * 1000\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct fromHours(h) {\n"
|
||||
" _ms = h * 60 * 60 * 1000\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct fromDays(d) {\n"
|
||||
" _ms = d * 24 * 60 * 60 * 1000\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" milliseconds { _ms }\n"
|
||||
" seconds { _ms / 1000 }\n"
|
||||
" minutes { _ms / 60000 }\n"
|
||||
" hours { _ms / 3600000 }\n"
|
||||
" days { _ms / 86400000 }\n"
|
||||
"\n"
|
||||
" +(other) {\n"
|
||||
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
|
||||
" return Duration.fromMilliseconds(_ms + other.milliseconds)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" -(other) {\n"
|
||||
" if (!(other is Duration)) Fiber.abort(\"Expected Duration.\")\n"
|
||||
" return Duration.fromMilliseconds(_ms - other.milliseconds)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" *(factor) {\n"
|
||||
" return Duration.fromMilliseconds(_ms * factor)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ==(other) {\n"
|
||||
" if (!(other is Duration)) return false\n"
|
||||
" return _ms == other.milliseconds\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toString { \"%(days.floor)d %(hours.floor % 24)h %(minutes.floor % 60)m %(seconds.floor % 60)s\" }\n"
|
||||
"}\n";
|
||||
83
src/module/dns.c
Normal file
83
src/module/dns.c
Normal file
@ -0,0 +1,83 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dns.h"
|
||||
#include "scheduler.h"
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
#include "uv.h"
|
||||
|
||||
static void dnsCallback(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
|
||||
WrenHandle* fiber = (WrenHandle*)req->data;
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
if (status == 0 && res != NULL) {
|
||||
struct addrinfo* addr = res;
|
||||
while (addr != NULL) {
|
||||
char ip[64];
|
||||
if (addr->ai_family == AF_INET) {
|
||||
struct sockaddr_in* sa = (struct sockaddr_in*)addr->ai_addr;
|
||||
uv_ip4_name(sa, ip, sizeof(ip));
|
||||
wrenSetSlotString(vm, 1, ip);
|
||||
wrenInsertInList(vm, 2, -1, 1);
|
||||
} else if (addr->ai_family == AF_INET6) {
|
||||
struct sockaddr_in6* sa = (struct sockaddr_in6*)addr->ai_addr;
|
||||
uv_ip6_name(sa, ip, sizeof(ip));
|
||||
wrenSetSlotString(vm, 1, ip);
|
||||
wrenInsertInList(vm, 2, -1, 1);
|
||||
}
|
||||
addr = addr->ai_next;
|
||||
}
|
||||
uv_freeaddrinfo(res);
|
||||
}
|
||||
|
||||
free(req);
|
||||
schedulerResume(fiber, true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void dnsLookup(WrenVM* vm) {
|
||||
const char* hostname = wrenGetSlotString(vm, 1);
|
||||
int family = (int)wrenGetSlotDouble(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
|
||||
if (hostname == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Hostname required.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_getaddrinfo_t* req = (uv_getaddrinfo_t*)malloc(sizeof(uv_getaddrinfo_t));
|
||||
if (req == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
req->data = fiber;
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (family == 4) {
|
||||
hints.ai_family = AF_INET;
|
||||
} else if (family == 6) {
|
||||
hints.ai_family = AF_INET6;
|
||||
} else {
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
}
|
||||
|
||||
int result = uv_getaddrinfo(getLoop(), req, dnsCallback, hostname, NULL, &hints);
|
||||
if (result != 0) {
|
||||
free(req);
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, uv_strerror(result));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
10
src/module/dns.h
Normal file
10
src/module/dns.h
Normal file
@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_dns_h
|
||||
#define wren_dns_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void dnsLookup(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
17
src/module/dns.wren
vendored
Normal file
17
src/module/dns.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class Dns {
|
||||
static lookup(hostname) { lookup(hostname, 0) }
|
||||
|
||||
static lookup(hostname, family) {
|
||||
if (!(hostname is String)) Fiber.abort("Hostname must be a string.")
|
||||
if (family != 0 && family != 4 && family != 6) {
|
||||
Fiber.abort("Family must be 0 (any), 4 (IPv4), or 6 (IPv6).")
|
||||
}
|
||||
return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static lookup_(hostname, family, fiber)
|
||||
}
|
||||
21
src/module/dns.wren.inc
Normal file
21
src/module/dns.wren.inc
Normal file
@ -0,0 +1,21 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/dns.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* dnsModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class Dns {\n"
|
||||
" static lookup(hostname) { lookup(hostname, 0) }\n"
|
||||
"\n"
|
||||
" static lookup(hostname, family) {\n"
|
||||
" if (!(hostname is String)) Fiber.abort(\"Hostname must be a string.\")\n"
|
||||
" if (family != 0 && family != 4 && family != 6) {\n"
|
||||
" Fiber.abort(\"Family must be 0 (any), 4 (IPv4), or 6 (IPv6).\")\n"
|
||||
" }\n"
|
||||
" return Scheduler.await_ { lookup_(hostname, family, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static lookup_(hostname, family, fiber)\n"
|
||||
"}\n";
|
||||
108
src/module/env.c
Normal file
108
src/module/env.c
Normal file
@ -0,0 +1,108 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "env.h"
|
||||
#include "wren.h"
|
||||
#include "uv.h"
|
||||
|
||||
void envGet(WrenVM* vm) {
|
||||
const char* name = wrenGetSlotString(vm, 1);
|
||||
if (name == NULL) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
size_t size = sizeof(buffer);
|
||||
int result = uv_os_getenv(name, buffer, &size);
|
||||
|
||||
if (result == UV_ENOENT) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == UV_ENOBUFS) {
|
||||
char* largeBuffer = (char*)malloc(size);
|
||||
if (largeBuffer == NULL) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
result = uv_os_getenv(name, largeBuffer, &size);
|
||||
if (result == 0) {
|
||||
wrenSetSlotString(vm, 0, largeBuffer);
|
||||
} else {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
free(largeBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotString(vm, 0, buffer);
|
||||
}
|
||||
|
||||
void envSet(WrenVM* vm) {
|
||||
const char* name = wrenGetSlotString(vm, 1);
|
||||
const char* value = wrenGetSlotString(vm, 2);
|
||||
|
||||
if (name == NULL || value == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Name and value must be strings.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int result = uv_os_setenv(name, value);
|
||||
if (result != 0) {
|
||||
wrenSetSlotString(vm, 0, "Failed to set environment variable.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void envDelete(WrenVM* vm) {
|
||||
const char* name = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (name == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Name must be a string.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int result = uv_os_unsetenv(name);
|
||||
if (result != 0) {
|
||||
wrenSetSlotString(vm, 0, "Failed to delete environment variable.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void envAll(WrenVM* vm) {
|
||||
uv_env_item_t* envItems = NULL;
|
||||
int count = 0;
|
||||
|
||||
int result = uv_os_environ(&envItems, &count);
|
||||
if (result != 0) {
|
||||
wrenSetSlotNewMap(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewMap(vm, 0);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
wrenSetSlotString(vm, 1, envItems[i].name);
|
||||
wrenSetSlotString(vm, 2, envItems[i].value);
|
||||
wrenSetMapValue(vm, 0, 1, 2);
|
||||
}
|
||||
|
||||
uv_os_free_environ(envItems, count);
|
||||
}
|
||||
13
src/module/env.h
Normal file
13
src/module/env.h
Normal file
@ -0,0 +1,13 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_env_h
|
||||
#define wren_env_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void envGet(WrenVM* vm);
|
||||
void envSet(WrenVM* vm);
|
||||
void envDelete(WrenVM* vm);
|
||||
void envAll(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
8
src/module/env.wren
vendored
Normal file
8
src/module/env.wren
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Environment {
|
||||
foreign static get(name)
|
||||
foreign static set(name, value)
|
||||
foreign static delete(name)
|
||||
foreign static all
|
||||
}
|
||||
12
src/module/env.wren.inc
Normal file
12
src/module/env.wren.inc
Normal file
@ -0,0 +1,12 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/env.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* envModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Environment {\n"
|
||||
" foreign static get(name)\n"
|
||||
" foreign static set(name, value)\n"
|
||||
" foreign static delete(name)\n"
|
||||
" foreign static all\n"
|
||||
"}\n";
|
||||
304
src/module/http.wren
vendored
Normal file
304
src/module/http.wren
vendored
Normal file
@ -0,0 +1,304 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "net" for Socket
|
||||
import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
import "json" for Json
|
||||
import "base64" for Base64
|
||||
|
||||
class Url {
|
||||
construct parse(url) {
|
||||
_scheme = "http"
|
||||
_host = ""
|
||||
_port = 80
|
||||
_path = "/"
|
||||
_query = ""
|
||||
|
||||
var rest = url
|
||||
|
||||
var schemeEnd = rest.indexOf("://")
|
||||
if (schemeEnd >= 0) {
|
||||
_scheme = Url.toLower_(rest[0...schemeEnd])
|
||||
rest = rest[(schemeEnd + 3)..-1]
|
||||
if (_scheme == "https") _port = 443
|
||||
}
|
||||
|
||||
var pathStart = rest.indexOf("/")
|
||||
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
|
||||
var pathPart = pathStart >= 0 ? rest[pathStart..-1] : "/"
|
||||
|
||||
var queryStart = pathPart.indexOf("?")
|
||||
if (queryStart >= 0) {
|
||||
_query = pathPart[(queryStart + 1)..-1]
|
||||
_path = pathPart[0...queryStart]
|
||||
} else {
|
||||
_path = pathPart
|
||||
}
|
||||
|
||||
var portStart = hostPart.indexOf(":")
|
||||
if (portStart >= 0) {
|
||||
_host = hostPart[0...portStart]
|
||||
_port = Num.fromString(hostPart[(portStart + 1)..-1])
|
||||
} else {
|
||||
_host = hostPart
|
||||
}
|
||||
}
|
||||
|
||||
static toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
scheme { _scheme }
|
||||
host { _host }
|
||||
port { _port }
|
||||
path { _path }
|
||||
query { _query }
|
||||
|
||||
fullPath {
|
||||
if (_query.count > 0) return _path + "?" + _query
|
||||
return _path
|
||||
}
|
||||
}
|
||||
|
||||
class HttpResponse {
|
||||
construct new_(statusCode, statusText, headers, body) {
|
||||
_statusCode = statusCode
|
||||
_statusText = statusText
|
||||
_headers = headers
|
||||
_body = body
|
||||
}
|
||||
|
||||
statusCode { _statusCode }
|
||||
statusText { _statusText }
|
||||
headers { _headers }
|
||||
body { _body }
|
||||
ok { _statusCode >= 200 && _statusCode < 300 }
|
||||
|
||||
header(name) {
|
||||
var lower = Url.toLower_(name)
|
||||
for (entry in _headers) {
|
||||
if (Url.toLower_(entry.key) == lower) return entry.value
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
json {
|
||||
if (_body == null || _body.count == 0) return null
|
||||
return Json.parse(_body)
|
||||
}
|
||||
|
||||
toString { "HttpResponse(%(statusCode) %(statusText))" }
|
||||
}
|
||||
|
||||
class Http {
|
||||
static get(url) { request(url, "GET", null, {}) }
|
||||
static get(url, headers) { request(url, "GET", null, headers) }
|
||||
|
||||
static post(url, body) { request(url, "POST", body, {}) }
|
||||
static post(url, body, headers) { request(url, "POST", body, headers) }
|
||||
|
||||
static put(url, body) { request(url, "PUT", body, {}) }
|
||||
static put(url, body, headers) { request(url, "PUT", body, headers) }
|
||||
|
||||
static delete(url) { request(url, "DELETE", null, {}) }
|
||||
static delete(url, headers) { request(url, "DELETE", null, headers) }
|
||||
|
||||
static patch(url, body) { request(url, "PATCH", body, {}) }
|
||||
static patch(url, body, headers) { request(url, "PATCH", body, headers) }
|
||||
|
||||
static request(url, method, body, headers) {
|
||||
var parsed = Url.parse(url)
|
||||
|
||||
if (parsed.scheme != "http" && parsed.scheme != "https") {
|
||||
Fiber.abort("Unsupported scheme: %(parsed.scheme)")
|
||||
}
|
||||
|
||||
var isHttps = parsed.scheme == "https"
|
||||
|
||||
var bodyStr = ""
|
||||
if (body != null) {
|
||||
if (body is String) {
|
||||
bodyStr = body
|
||||
} else if (body is Map || body is List) {
|
||||
bodyStr = Json.stringify(body)
|
||||
if (!hasHeader_(headers, "Content-Type")) {
|
||||
headers["Content-Type"] = "application/json"
|
||||
}
|
||||
} else {
|
||||
bodyStr = body.toString
|
||||
}
|
||||
}
|
||||
|
||||
var requestHeaders = {}
|
||||
requestHeaders["Host"] = parsed.host
|
||||
if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {
|
||||
requestHeaders["Host"] = "%(parsed.host):%(parsed.port)"
|
||||
}
|
||||
requestHeaders["Connection"] = "close"
|
||||
requestHeaders["User-Agent"] = "Wren-CLI/1.0"
|
||||
|
||||
if (bodyStr.count > 0) {
|
||||
requestHeaders["Content-Length"] = bodyStr.count.toString
|
||||
}
|
||||
|
||||
for (entry in headers) {
|
||||
requestHeaders[entry.key] = entry.value
|
||||
}
|
||||
|
||||
var request = "%(method) %(parsed.fullPath) HTTP/1.1\r\n"
|
||||
for (entry in requestHeaders) {
|
||||
request = request + "%(entry.key): %(entry.value)\r\n"
|
||||
}
|
||||
request = request + "\r\n" + bodyStr
|
||||
|
||||
var addresses = Dns.lookup(parsed.host, 4)
|
||||
if (addresses.count == 0) {
|
||||
Fiber.abort("Could not resolve host: %(parsed.host)")
|
||||
}
|
||||
|
||||
var socket
|
||||
if (isHttps) {
|
||||
socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)
|
||||
} else {
|
||||
socket = Socket.connect(addresses[0], parsed.port)
|
||||
}
|
||||
socket.write(request)
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) break
|
||||
response = response + chunk
|
||||
}
|
||||
socket.close()
|
||||
|
||||
return parseResponse_(response)
|
||||
}
|
||||
|
||||
static hasHeader_(headers, name) {
|
||||
var lower = Url.toLower_(name)
|
||||
for (entry in headers) {
|
||||
if (Url.toLower_(entry.key) == lower) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
static parseResponse_(response) {
|
||||
var headerEnd = response.indexOf("\r\n\r\n")
|
||||
if (headerEnd < 0) {
|
||||
Fiber.abort("Invalid HTTP response: no header/body separator")
|
||||
}
|
||||
|
||||
var headerPart = response[0...headerEnd]
|
||||
var body = response[(headerEnd + 4)..-1]
|
||||
|
||||
var lines = splitLines_(headerPart)
|
||||
if (lines.count == 0) {
|
||||
Fiber.abort("Invalid HTTP response: empty headers")
|
||||
}
|
||||
|
||||
var statusLine = lines[0]
|
||||
var statusParts = statusLine.split(" ")
|
||||
if (statusParts.count < 2) {
|
||||
Fiber.abort("Invalid HTTP status line")
|
||||
}
|
||||
|
||||
var statusCode = Num.fromString(statusParts[1])
|
||||
var statusText = statusParts.count > 2 ? statusParts[2..-1].join(" ") : ""
|
||||
|
||||
var headers = {}
|
||||
for (i in 1...lines.count) {
|
||||
var line = lines[i]
|
||||
var colonPos = line.indexOf(":")
|
||||
if (colonPos > 0) {
|
||||
var name = line[0...colonPos].trim()
|
||||
var value = line[(colonPos + 1)..-1].trim()
|
||||
headers[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
var transferEncoding = null
|
||||
for (entry in headers) {
|
||||
if (Url.toLower_(entry.key) == "transfer-encoding") {
|
||||
transferEncoding = Url.toLower_(entry.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (transferEncoding == "chunked") {
|
||||
body = decodeChunked_(body)
|
||||
}
|
||||
|
||||
return HttpResponse.new_(statusCode, statusText, headers, body)
|
||||
}
|
||||
|
||||
static splitLines_(text) {
|
||||
var lines = []
|
||||
var start = 0
|
||||
var i = 0
|
||||
while (i < text.count) {
|
||||
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
|
||||
lines.add(text[start...i])
|
||||
start = i + 2
|
||||
i = i + 2
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if (start < text.count) {
|
||||
lines.add(text[start..-1])
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
static decodeChunked_(body) {
|
||||
var result = ""
|
||||
var pos = 0
|
||||
|
||||
while (pos < body.count) {
|
||||
var lineEnd = body.indexOf("\r\n", pos)
|
||||
if (lineEnd < 0) break
|
||||
|
||||
var sizeStr = body[pos...lineEnd].trim()
|
||||
var size = parseHex_(sizeStr)
|
||||
|
||||
if (size == 0) break
|
||||
|
||||
pos = lineEnd + 2
|
||||
if (pos + size > body.count) break
|
||||
|
||||
result = result + body[pos...(pos + size)]
|
||||
pos = pos + size + 2
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static parseHex_(str) {
|
||||
var result = 0
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
var digit = 0
|
||||
if (cp >= 48 && cp <= 57) {
|
||||
digit = cp - 48
|
||||
} else if (cp >= 65 && cp <= 70) {
|
||||
digit = cp - 55
|
||||
} else if (cp >= 97 && cp <= 102) {
|
||||
digit = cp - 87
|
||||
} else {
|
||||
break
|
||||
}
|
||||
result = result * 16 + digit
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
308
src/module/http.wren.inc
Normal file
308
src/module/http.wren.inc
Normal file
@ -0,0 +1,308 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `/home/retoor/projects/wren-cli/src/module/http.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* httpModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"net\" for Socket\n"
|
||||
"import \"tls\" for TlsSocket\n"
|
||||
"import \"dns\" for Dns\n"
|
||||
"import \"json\" for Json\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"\n"
|
||||
"class Url {\n"
|
||||
" construct parse(url) {\n"
|
||||
" _scheme = \"http\"\n"
|
||||
" _host = \"\"\n"
|
||||
" _port = 80\n"
|
||||
" _path = \"/\"\n"
|
||||
" _query = \"\"\n"
|
||||
"\n"
|
||||
" var rest = url\n"
|
||||
"\n"
|
||||
" var schemeEnd = rest.indexOf(\"://\")\n"
|
||||
" if (schemeEnd >= 0) {\n"
|
||||
" _scheme = Url.toLower_(rest[0...schemeEnd])\n"
|
||||
" rest = rest[(schemeEnd + 3)..-1]\n"
|
||||
" if (_scheme == \"https\") _port = 443\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var pathStart = rest.indexOf(\"/\")\n"
|
||||
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
|
||||
" var pathPart = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
|
||||
"\n"
|
||||
" var queryStart = pathPart.indexOf(\"?\")\n"
|
||||
" if (queryStart >= 0) {\n"
|
||||
" _query = pathPart[(queryStart + 1)..-1]\n"
|
||||
" _path = pathPart[0...queryStart]\n"
|
||||
" } else {\n"
|
||||
" _path = pathPart\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var portStart = hostPart.indexOf(\":\")\n"
|
||||
" if (portStart >= 0) {\n"
|
||||
" _host = hostPart[0...portStart]\n"
|
||||
" _port = Num.fromString(hostPart[(portStart + 1)..-1])\n"
|
||||
" } else {\n"
|
||||
" _host = hostPart\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + String.fromCodePoint(cp + 32)\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" scheme { _scheme }\n"
|
||||
" host { _host }\n"
|
||||
" port { _port }\n"
|
||||
" path { _path }\n"
|
||||
" query { _query }\n"
|
||||
"\n"
|
||||
" fullPath {\n"
|
||||
" if (_query.count > 0) return _path + \"?\" + _query\n"
|
||||
" return _path\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class HttpResponse {\n"
|
||||
" construct new_(statusCode, statusText, headers, body) {\n"
|
||||
" _statusCode = statusCode\n"
|
||||
" _statusText = statusText\n"
|
||||
" _headers = headers\n"
|
||||
" _body = body\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" statusCode { _statusCode }\n"
|
||||
" statusText { _statusText }\n"
|
||||
" headers { _headers }\n"
|
||||
" body { _body }\n"
|
||||
" ok { _statusCode >= 200 && _statusCode < 300 }\n"
|
||||
"\n"
|
||||
" header(name) {\n"
|
||||
" var lower = Url.toLower_(name)\n"
|
||||
" for (entry in _headers) {\n"
|
||||
" if (Url.toLower_(entry.key) == lower) return entry.value\n"
|
||||
" }\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" json {\n"
|
||||
" if (_body == null || _body.count == 0) return null\n"
|
||||
" return Json.parse(_body)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toString { \"HttpResponse(%(statusCode) %(statusText))\" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Http {\n"
|
||||
" static get(url) { request(url, \"GET\", null, {}) }\n"
|
||||
" static get(url, headers) { request(url, \"GET\", null, headers) }\n"
|
||||
"\n"
|
||||
" static post(url, body) { request(url, \"POST\", body, {}) }\n"
|
||||
" static post(url, body, headers) { request(url, \"POST\", body, headers) }\n"
|
||||
"\n"
|
||||
" static put(url, body) { request(url, \"PUT\", body, {}) }\n"
|
||||
" static put(url, body, headers) { request(url, \"PUT\", body, headers) }\n"
|
||||
"\n"
|
||||
" static delete(url) { request(url, \"DELETE\", null, {}) }\n"
|
||||
" static delete(url, headers) { request(url, \"DELETE\", null, headers) }\n"
|
||||
"\n"
|
||||
" static patch(url, body) { request(url, \"PATCH\", body, {}) }\n"
|
||||
" static patch(url, body, headers) { request(url, \"PATCH\", body, headers) }\n"
|
||||
"\n"
|
||||
" static request(url, method, body, headers) {\n"
|
||||
" var parsed = Url.parse(url)\n"
|
||||
"\n"
|
||||
" if (parsed.scheme != \"http\" && parsed.scheme != \"https\") {\n"
|
||||
" Fiber.abort(\"Unsupported scheme: %(parsed.scheme)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var isHttps = parsed.scheme == \"https\"\n"
|
||||
"\n"
|
||||
" var bodyStr = \"\"\n"
|
||||
" if (body != null) {\n"
|
||||
" if (body is String) {\n"
|
||||
" bodyStr = body\n"
|
||||
" } else if (body is Map || body is List) {\n"
|
||||
" bodyStr = Json.stringify(body)\n"
|
||||
" if (!hasHeader_(headers, \"Content-Type\")) {\n"
|
||||
" headers[\"Content-Type\"] = \"application/json\"\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" bodyStr = body.toString\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var requestHeaders = {}\n"
|
||||
" requestHeaders[\"Host\"] = parsed.host\n"
|
||||
" if ((isHttps && parsed.port != 443) || (!isHttps && parsed.port != 80)) {\n"
|
||||
" requestHeaders[\"Host\"] = \"%(parsed.host):%(parsed.port)\"\n"
|
||||
" }\n"
|
||||
" requestHeaders[\"Connection\"] = \"close\"\n"
|
||||
" requestHeaders[\"User-Agent\"] = \"Wren-CLI/1.0\"\n"
|
||||
"\n"
|
||||
" if (bodyStr.count > 0) {\n"
|
||||
" requestHeaders[\"Content-Length\"] = bodyStr.count.toString\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (entry in headers) {\n"
|
||||
" requestHeaders[entry.key] = entry.value\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var request = \"%(method) %(parsed.fullPath) HTTP/1.1\\r\\n\"\n"
|
||||
" for (entry in requestHeaders) {\n"
|
||||
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
|
||||
" }\n"
|
||||
" request = request + \"\\r\\n\" + bodyStr\n"
|
||||
"\n"
|
||||
" var addresses = Dns.lookup(parsed.host, 4)\n"
|
||||
" if (addresses.count == 0) {\n"
|
||||
" Fiber.abort(\"Could not resolve host: %(parsed.host)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var socket\n"
|
||||
" if (isHttps) {\n"
|
||||
" socket = TlsSocket.connect(addresses[0], parsed.port, parsed.host)\n"
|
||||
" } else {\n"
|
||||
" socket = Socket.connect(addresses[0], parsed.port)\n"
|
||||
" }\n"
|
||||
" socket.write(request)\n"
|
||||
"\n"
|
||||
" var response = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) break\n"
|
||||
" response = response + chunk\n"
|
||||
" }\n"
|
||||
" socket.close()\n"
|
||||
"\n"
|
||||
" return parseResponse_(response)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hasHeader_(headers, name) {\n"
|
||||
" var lower = Url.toLower_(name)\n"
|
||||
" for (entry in headers) {\n"
|
||||
" if (Url.toLower_(entry.key) == lower) return true\n"
|
||||
" }\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseResponse_(response) {\n"
|
||||
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
|
||||
" if (headerEnd < 0) {\n"
|
||||
" Fiber.abort(\"Invalid HTTP response: no header/body separator\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var headerPart = response[0...headerEnd]\n"
|
||||
" var body = response[(headerEnd + 4)..-1]\n"
|
||||
"\n"
|
||||
" var lines = splitLines_(headerPart)\n"
|
||||
" if (lines.count == 0) {\n"
|
||||
" Fiber.abort(\"Invalid HTTP response: empty headers\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var statusLine = lines[0]\n"
|
||||
" var statusParts = statusLine.split(\" \")\n"
|
||||
" if (statusParts.count < 2) {\n"
|
||||
" Fiber.abort(\"Invalid HTTP status line\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var statusCode = Num.fromString(statusParts[1])\n"
|
||||
" var statusText = statusParts.count > 2 ? statusParts[2..-1].join(\" \") : \"\"\n"
|
||||
"\n"
|
||||
" var headers = {}\n"
|
||||
" for (i in 1...lines.count) {\n"
|
||||
" var line = lines[i]\n"
|
||||
" var colonPos = line.indexOf(\":\")\n"
|
||||
" if (colonPos > 0) {\n"
|
||||
" var name = line[0...colonPos].trim()\n"
|
||||
" var value = line[(colonPos + 1)..-1].trim()\n"
|
||||
" headers[name] = value\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var transferEncoding = null\n"
|
||||
" for (entry in headers) {\n"
|
||||
" if (Url.toLower_(entry.key) == \"transfer-encoding\") {\n"
|
||||
" transferEncoding = Url.toLower_(entry.value)\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (transferEncoding == \"chunked\") {\n"
|
||||
" body = decodeChunked_(body)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return HttpResponse.new_(statusCode, statusText, headers, body)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static splitLines_(text) {\n"
|
||||
" var lines = []\n"
|
||||
" var start = 0\n"
|
||||
" var i = 0\n"
|
||||
" while (i < text.count) {\n"
|
||||
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
|
||||
" lines.add(text[start...i])\n"
|
||||
" start = i + 2\n"
|
||||
" i = i + 2\n"
|
||||
" } else {\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (start < text.count) {\n"
|
||||
" lines.add(text[start..-1])\n"
|
||||
" }\n"
|
||||
" return lines\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeChunked_(body) {\n"
|
||||
" var result = \"\"\n"
|
||||
" var pos = 0\n"
|
||||
"\n"
|
||||
" while (pos < body.count) {\n"
|
||||
" var lineEnd = body.indexOf(\"\\r\\n\", pos)\n"
|
||||
" if (lineEnd < 0) break\n"
|
||||
"\n"
|
||||
" var sizeStr = body[pos...lineEnd].trim()\n"
|
||||
" var size = parseHex_(sizeStr)\n"
|
||||
"\n"
|
||||
" if (size == 0) break\n"
|
||||
"\n"
|
||||
" pos = lineEnd + 2\n"
|
||||
" if (pos + size > body.count) break\n"
|
||||
"\n"
|
||||
" result = result + body[pos...(pos + size)]\n"
|
||||
" pos = pos + size + 2\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseHex_(str) {\n"
|
||||
" var result = 0\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" var digit = 0\n"
|
||||
" if (cp >= 48 && cp <= 57) {\n"
|
||||
" digit = cp - 48\n"
|
||||
" } else if (cp >= 65 && cp <= 70) {\n"
|
||||
" digit = cp - 55\n"
|
||||
" } else if (cp >= 97 && cp <= 102) {\n"
|
||||
" digit = cp - 87\n"
|
||||
" } else {\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" result = result * 16 + digit\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
2649
src/module/jinja.wren
vendored
Normal file
2649
src/module/jinja.wren
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2653
src/module/jinja.wren.inc
Normal file
2653
src/module/jinja.wren.inc
Normal file
File diff suppressed because it is too large
Load Diff
98
src/module/json.c
Normal file
98
src/module/json.c
Normal file
@ -0,0 +1,98 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "json.h"
|
||||
#include "wren.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
static void cjsonToWren(WrenVM* vm, int slot, cJSON* item) {
|
||||
if (item == NULL) {
|
||||
wrenSetSlotNull(vm, slot);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (item->type & 0xFF) {
|
||||
case cJSON_NULL:
|
||||
wrenSetSlotNull(vm, slot);
|
||||
break;
|
||||
|
||||
case cJSON_False:
|
||||
wrenSetSlotBool(vm, slot, false);
|
||||
break;
|
||||
|
||||
case cJSON_True:
|
||||
wrenSetSlotBool(vm, slot, true);
|
||||
break;
|
||||
|
||||
case cJSON_Number:
|
||||
wrenSetSlotDouble(vm, slot, item->valuedouble);
|
||||
break;
|
||||
|
||||
case cJSON_String:
|
||||
wrenSetSlotString(vm, slot, item->valuestring ? item->valuestring : "");
|
||||
break;
|
||||
|
||||
case cJSON_Array: {
|
||||
wrenSetSlotNewList(vm, slot);
|
||||
int elemSlot = slot + 1;
|
||||
wrenEnsureSlots(vm, elemSlot + 1);
|
||||
|
||||
cJSON* child = item->child;
|
||||
while (child != NULL) {
|
||||
cjsonToWren(vm, elemSlot, child);
|
||||
wrenInsertInList(vm, slot, -1, elemSlot);
|
||||
child = child->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case cJSON_Object: {
|
||||
wrenSetSlotNewMap(vm, slot);
|
||||
int keySlot = slot + 1;
|
||||
int valueSlot = slot + 2;
|
||||
wrenEnsureSlots(vm, valueSlot + 1);
|
||||
|
||||
cJSON* child = item->child;
|
||||
while (child != NULL) {
|
||||
wrenSetSlotString(vm, keySlot, child->string ? child->string : "");
|
||||
cjsonToWren(vm, valueSlot, child);
|
||||
wrenSetMapValue(vm, slot, keySlot, valueSlot);
|
||||
child = child->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
wrenSetSlotNull(vm, slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonParse(WrenVM* vm) {
|
||||
const char* jsonString = wrenGetSlotString(vm, 1);
|
||||
if (jsonString == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Expected string argument.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* json = cJSON_Parse(jsonString);
|
||||
if (json == NULL) {
|
||||
const char* error = cJSON_GetErrorPtr();
|
||||
if (error != NULL) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "JSON parse error near: %.50s", error);
|
||||
wrenSetSlotString(vm, 0, msg);
|
||||
} else {
|
||||
wrenSetSlotString(vm, 0, "JSON parse error.");
|
||||
}
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 4);
|
||||
cjsonToWren(vm, 0, json);
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
10
src/module/json.h
Normal file
10
src/module/json.h
Normal file
@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_json_h
|
||||
#define wren_json_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void jsonParse(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
99
src/module/json.wren
vendored
Normal file
99
src/module/json.wren
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Json {
|
||||
foreign static parse(string)
|
||||
|
||||
static stringify(value) { stringify_(value, "") }
|
||||
|
||||
static stringify(value, indent) {
|
||||
var indentStr = ""
|
||||
if (indent is Num && indent > 0) {
|
||||
for (i in 0...indent) {
|
||||
indentStr = indentStr + " "
|
||||
}
|
||||
} else if (indent is String) {
|
||||
indentStr = indent
|
||||
}
|
||||
return stringify_(value, indentStr)
|
||||
}
|
||||
|
||||
static stringify_(value, indent) {
|
||||
return stringifyValue_(value, indent, "")
|
||||
}
|
||||
|
||||
static stringifyValue_(value, indent, currentIndent) {
|
||||
if (value == null) return "null"
|
||||
if (value is Bool) return value ? "true" : "false"
|
||||
if (value is Num) {
|
||||
if (value.isInfinity || value.isNan) return "null"
|
||||
if (value == value.truncate) return value.truncate.toString
|
||||
return value.toString
|
||||
}
|
||||
if (value is String) return escapeString_(value)
|
||||
if (value is List) return stringifyList_(value, indent, currentIndent)
|
||||
if (value is Map) return stringifyMap_(value, indent, currentIndent)
|
||||
return "null"
|
||||
}
|
||||
|
||||
static escapeString_(s) {
|
||||
var result = "\""
|
||||
for (c in s) {
|
||||
if (c == "\"") {
|
||||
result = result + "\\\""
|
||||
} else if (c == "\\") {
|
||||
result = result + "\\\\"
|
||||
} else if (c == "\b") {
|
||||
result = result + "\\b"
|
||||
} else if (c == "\f") {
|
||||
result = result + "\\f"
|
||||
} else if (c == "\n") {
|
||||
result = result + "\\n"
|
||||
} else if (c == "\r") {
|
||||
result = result + "\\r"
|
||||
} else if (c == "\t") {
|
||||
result = result + "\\t"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result + "\""
|
||||
}
|
||||
|
||||
static stringifyList_(list, indent, currentIndent) {
|
||||
if (list.count == 0) return "[]"
|
||||
|
||||
var nextIndent = currentIndent + indent
|
||||
var parts = []
|
||||
for (item in list) {
|
||||
parts.add(stringifyValue_(item, indent, nextIndent))
|
||||
}
|
||||
|
||||
if (indent == "") {
|
||||
return "[" + parts.join(",") + "]"
|
||||
}
|
||||
|
||||
return "[\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "]"
|
||||
}
|
||||
|
||||
static stringifyMap_(map, indent, currentIndent) {
|
||||
if (map.count == 0) return "{}"
|
||||
|
||||
var nextIndent = currentIndent + indent
|
||||
var parts = []
|
||||
for (key in map.keys) {
|
||||
var keyStr = escapeString_(key.toString)
|
||||
var valueStr = stringifyValue_(map[key], indent, nextIndent)
|
||||
if (indent == "") {
|
||||
parts.add(keyStr + ":" + valueStr)
|
||||
} else {
|
||||
parts.add(keyStr + ": " + valueStr)
|
||||
}
|
||||
}
|
||||
|
||||
if (indent == "") {
|
||||
return "{" + parts.join(",") + "}"
|
||||
}
|
||||
|
||||
return "{\n" + nextIndent + parts.join(",\n" + nextIndent) + "\n" + currentIndent + "}"
|
||||
}
|
||||
}
|
||||
103
src/module/json.wren.inc
Normal file
103
src/module/json.wren.inc
Normal file
@ -0,0 +1,103 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/json.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* jsonModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Json {\n"
|
||||
" foreign static parse(string)\n"
|
||||
"\n"
|
||||
" static stringify(value) { stringify_(value, \"\") }\n"
|
||||
"\n"
|
||||
" static stringify(value, indent) {\n"
|
||||
" var indentStr = \"\"\n"
|
||||
" if (indent is Num && indent > 0) {\n"
|
||||
" for (i in 0...indent) {\n"
|
||||
" indentStr = indentStr + \" \"\n"
|
||||
" }\n"
|
||||
" } else if (indent is String) {\n"
|
||||
" indentStr = indent\n"
|
||||
" }\n"
|
||||
" return stringify_(value, indentStr)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stringify_(value, indent) {\n"
|
||||
" return stringifyValue_(value, indent, \"\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stringifyValue_(value, indent, currentIndent) {\n"
|
||||
" if (value == null) return \"null\"\n"
|
||||
" if (value is Bool) return value ? \"true\" : \"false\"\n"
|
||||
" if (value is Num) {\n"
|
||||
" if (value.isInfinity || value.isNan) return \"null\"\n"
|
||||
" if (value == value.truncate) return value.truncate.toString\n"
|
||||
" return value.toString\n"
|
||||
" }\n"
|
||||
" if (value is String) return escapeString_(value)\n"
|
||||
" if (value is List) return stringifyList_(value, indent, currentIndent)\n"
|
||||
" if (value is Map) return stringifyMap_(value, indent, currentIndent)\n"
|
||||
" return \"null\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static escapeString_(s) {\n"
|
||||
" var result = \"\\\"\"\n"
|
||||
" for (c in s) {\n"
|
||||
" if (c == \"\\\"\") {\n"
|
||||
" result = result + \"\\\\\\\"\"\n"
|
||||
" } else if (c == \"\\\\\") {\n"
|
||||
" result = result + \"\\\\\\\\\"\n"
|
||||
" } else if (c == \"\\b\") {\n"
|
||||
" result = result + \"\\\\b\"\n"
|
||||
" } else if (c == \"\\f\") {\n"
|
||||
" result = result + \"\\\\f\"\n"
|
||||
" } else if (c == \"\\n\") {\n"
|
||||
" result = result + \"\\\\n\"\n"
|
||||
" } else if (c == \"\\r\") {\n"
|
||||
" result = result + \"\\\\r\"\n"
|
||||
" } else if (c == \"\\t\") {\n"
|
||||
" result = result + \"\\\\t\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result + \"\\\"\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stringifyList_(list, indent, currentIndent) {\n"
|
||||
" if (list.count == 0) return \"[]\"\n"
|
||||
"\n"
|
||||
" var nextIndent = currentIndent + indent\n"
|
||||
" var parts = []\n"
|
||||
" for (item in list) {\n"
|
||||
" parts.add(stringifyValue_(item, indent, nextIndent))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (indent == \"\") {\n"
|
||||
" return \"[\" + parts.join(\",\") + \"]\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return \"[\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"]\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stringifyMap_(map, indent, currentIndent) {\n"
|
||||
" if (map.count == 0) return \"{}\"\n"
|
||||
"\n"
|
||||
" var nextIndent = currentIndent + indent\n"
|
||||
" var parts = []\n"
|
||||
" for (key in map.keys) {\n"
|
||||
" var keyStr = escapeString_(key.toString)\n"
|
||||
" var valueStr = stringifyValue_(map[key], indent, nextIndent)\n"
|
||||
" if (indent == \"\") {\n"
|
||||
" parts.add(keyStr + \":\" + valueStr)\n"
|
||||
" } else {\n"
|
||||
" parts.add(keyStr + \": \" + valueStr)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (indent == \"\") {\n"
|
||||
" return \"{\" + parts.join(\",\") + \"}\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return \"{\\n\" + nextIndent + parts.join(\",\\n\" + nextIndent) + \"\\n\" + currentIndent + \"}\"\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
46
src/module/math.wren
vendored
Normal file
46
src/module/math.wren
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Math {
|
||||
foreign static sin(x)
|
||||
foreign static cos(x)
|
||||
foreign static tan(x)
|
||||
foreign static asin(x)
|
||||
foreign static acos(x)
|
||||
foreign static atan(x)
|
||||
foreign static atan2(y, x)
|
||||
foreign static sinh(x)
|
||||
foreign static cosh(x)
|
||||
foreign static tanh(x)
|
||||
foreign static log(x)
|
||||
foreign static log10(x)
|
||||
foreign static log2(x)
|
||||
foreign static exp(x)
|
||||
foreign static pow(base, exp)
|
||||
foreign static sqrt(x)
|
||||
foreign static cbrt(x)
|
||||
foreign static ceil(x)
|
||||
foreign static floor(x)
|
||||
foreign static round(x)
|
||||
foreign static abs(x)
|
||||
|
||||
static min(a, b) { a < b ? a : b }
|
||||
static max(a, b) { a > b ? a : b }
|
||||
static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }
|
||||
|
||||
static pi { 3.14159265358979323846 }
|
||||
static e { 2.71828182845904523536 }
|
||||
static tau { 6.28318530717958647692 }
|
||||
static infinity { 1 / 0 }
|
||||
static nan { 0 / 0 }
|
||||
|
||||
static degrees(radians) { radians * 180 / pi }
|
||||
static radians(degrees) { degrees * pi / 180 }
|
||||
|
||||
static sign(x) {
|
||||
if (x > 0) return 1
|
||||
if (x < 0) return -1
|
||||
return 0
|
||||
}
|
||||
|
||||
static lerp(a, b, t) { a + (b - a) * t }
|
||||
}
|
||||
50
src/module/math.wren.inc
Normal file
50
src/module/math.wren.inc
Normal file
@ -0,0 +1,50 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/math.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* mathModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Math {\n"
|
||||
" foreign static sin(x)\n"
|
||||
" foreign static cos(x)\n"
|
||||
" foreign static tan(x)\n"
|
||||
" foreign static asin(x)\n"
|
||||
" foreign static acos(x)\n"
|
||||
" foreign static atan(x)\n"
|
||||
" foreign static atan2(y, x)\n"
|
||||
" foreign static sinh(x)\n"
|
||||
" foreign static cosh(x)\n"
|
||||
" foreign static tanh(x)\n"
|
||||
" foreign static log(x)\n"
|
||||
" foreign static log10(x)\n"
|
||||
" foreign static log2(x)\n"
|
||||
" foreign static exp(x)\n"
|
||||
" foreign static pow(base, exp)\n"
|
||||
" foreign static sqrt(x)\n"
|
||||
" foreign static cbrt(x)\n"
|
||||
" foreign static ceil(x)\n"
|
||||
" foreign static floor(x)\n"
|
||||
" foreign static round(x)\n"
|
||||
" foreign static abs(x)\n"
|
||||
"\n"
|
||||
" static min(a, b) { a < b ? a : b }\n"
|
||||
" static max(a, b) { a > b ? a : b }\n"
|
||||
" static clamp(x, min, max) { x < min ? min : (x > max ? max : x) }\n"
|
||||
"\n"
|
||||
" static pi { 3.14159265358979323846 }\n"
|
||||
" static e { 2.71828182845904523536 }\n"
|
||||
" static tau { 6.28318530717958647692 }\n"
|
||||
" static infinity { 1 / 0 }\n"
|
||||
" static nan { 0 / 0 }\n"
|
||||
"\n"
|
||||
" static degrees(radians) { radians * 180 / pi }\n"
|
||||
" static radians(degrees) { degrees * pi / 180 }\n"
|
||||
"\n"
|
||||
" static sign(x) {\n"
|
||||
" if (x > 0) return 1\n"
|
||||
" if (x < 0) return -1\n"
|
||||
" return 0\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static lerp(a, b, t) { a + (b - a) * t }\n"
|
||||
"}\n";
|
||||
112
src/module/math_module.c
Normal file
112
src/module/math_module.c
Normal file
@ -0,0 +1,112 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <math.h>
|
||||
#include "math_module.h"
|
||||
#include "wren.h"
|
||||
|
||||
void mathSin(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, sin(x));
|
||||
}
|
||||
|
||||
void mathCos(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, cos(x));
|
||||
}
|
||||
|
||||
void mathTan(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, tan(x));
|
||||
}
|
||||
|
||||
void mathAsin(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, asin(x));
|
||||
}
|
||||
|
||||
void mathAcos(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, acos(x));
|
||||
}
|
||||
|
||||
void mathAtan(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, atan(x));
|
||||
}
|
||||
|
||||
void mathAtan2(WrenVM* vm) {
|
||||
double y = wrenGetSlotDouble(vm, 1);
|
||||
double x = wrenGetSlotDouble(vm, 2);
|
||||
wrenSetSlotDouble(vm, 0, atan2(y, x));
|
||||
}
|
||||
|
||||
void mathSinh(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, sinh(x));
|
||||
}
|
||||
|
||||
void mathCosh(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, cosh(x));
|
||||
}
|
||||
|
||||
void mathTanh(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, tanh(x));
|
||||
}
|
||||
|
||||
void mathLog(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, log(x));
|
||||
}
|
||||
|
||||
void mathLog10(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, log10(x));
|
||||
}
|
||||
|
||||
void mathLog2(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, log2(x));
|
||||
}
|
||||
|
||||
void mathExp(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, exp(x));
|
||||
}
|
||||
|
||||
void mathPow(WrenVM* vm) {
|
||||
double base = wrenGetSlotDouble(vm, 1);
|
||||
double exponent = wrenGetSlotDouble(vm, 2);
|
||||
wrenSetSlotDouble(vm, 0, pow(base, exponent));
|
||||
}
|
||||
|
||||
void mathSqrt(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, sqrt(x));
|
||||
}
|
||||
|
||||
void mathCbrt(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, cbrt(x));
|
||||
}
|
||||
|
||||
void mathCeil(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, ceil(x));
|
||||
}
|
||||
|
||||
void mathFloor(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, floor(x));
|
||||
}
|
||||
|
||||
void mathRound(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, round(x));
|
||||
}
|
||||
|
||||
void mathAbs(WrenVM* vm) {
|
||||
double x = wrenGetSlotDouble(vm, 1);
|
||||
wrenSetSlotDouble(vm, 0, fabs(x));
|
||||
}
|
||||
30
src/module/math_module.h
Normal file
30
src/module/math_module.h
Normal file
@ -0,0 +1,30 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_math_module_h
|
||||
#define wren_math_module_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void mathSin(WrenVM* vm);
|
||||
void mathCos(WrenVM* vm);
|
||||
void mathTan(WrenVM* vm);
|
||||
void mathAsin(WrenVM* vm);
|
||||
void mathAcos(WrenVM* vm);
|
||||
void mathAtan(WrenVM* vm);
|
||||
void mathAtan2(WrenVM* vm);
|
||||
void mathSinh(WrenVM* vm);
|
||||
void mathCosh(WrenVM* vm);
|
||||
void mathTanh(WrenVM* vm);
|
||||
void mathLog(WrenVM* vm);
|
||||
void mathLog10(WrenVM* vm);
|
||||
void mathLog2(WrenVM* vm);
|
||||
void mathExp(WrenVM* vm);
|
||||
void mathPow(WrenVM* vm);
|
||||
void mathSqrt(WrenVM* vm);
|
||||
void mathCbrt(WrenVM* vm);
|
||||
void mathCeil(WrenVM* vm);
|
||||
void mathFloor(WrenVM* vm);
|
||||
void mathRound(WrenVM* vm);
|
||||
void mathAbs(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
@ -159,9 +159,8 @@ void socketWrite(WrenVM* vm)
|
||||
|
||||
SocketData* data = (SocketData*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
|
||||
size_t length = strlen(text);
|
||||
int length;
|
||||
const char* text = wrenGetSlotBytes(vm, 1, &length);
|
||||
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
|
||||
447
src/module/regex.c
Normal file
447
src/module/regex.c
Normal file
@ -0,0 +1,447 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "regex.h"
|
||||
#include "wren.h"
|
||||
|
||||
typedef struct {
|
||||
char* pattern;
|
||||
char* flags;
|
||||
bool ignoreCase;
|
||||
bool global;
|
||||
bool multiline;
|
||||
} RegexData;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
char literal;
|
||||
const char* classStart;
|
||||
const char* classEnd;
|
||||
} PatternElement;
|
||||
|
||||
#define ELEM_LITERAL 0
|
||||
#define ELEM_DOT 1
|
||||
#define ELEM_CLASS 2
|
||||
#define ELEM_ESCAPE 3
|
||||
|
||||
static bool matchElement(PatternElement* elem, char c, bool ignoreCase) {
|
||||
if (c == '\0') return false;
|
||||
|
||||
if (elem->type == ELEM_DOT) {
|
||||
return c != '\n';
|
||||
}
|
||||
|
||||
if (elem->type == ELEM_LITERAL) {
|
||||
char p = elem->literal;
|
||||
if (ignoreCase) {
|
||||
if (p >= 'A' && p <= 'Z') p += 32;
|
||||
if (c >= 'A' && c <= 'Z') c += 32;
|
||||
}
|
||||
return p == c;
|
||||
}
|
||||
|
||||
if (elem->type == ELEM_ESCAPE) {
|
||||
switch (elem->literal) {
|
||||
case 'd': return (c >= '0' && c <= '9');
|
||||
case 'D': return !(c >= '0' && c <= '9');
|
||||
case 'w': return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_';
|
||||
case 'W': return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_');
|
||||
case 's': return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
|
||||
case 'S': return !(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f');
|
||||
case 'n': return c == '\n';
|
||||
case 't': return c == '\t';
|
||||
case 'r': return c == '\r';
|
||||
default: {
|
||||
char p = elem->literal;
|
||||
if (ignoreCase) {
|
||||
if (p >= 'A' && p <= 'Z') p += 32;
|
||||
if (c >= 'A' && c <= 'Z') c += 32;
|
||||
}
|
||||
return p == c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elem->type == ELEM_CLASS) {
|
||||
const char* p = elem->classStart;
|
||||
bool negate = false;
|
||||
bool matched = false;
|
||||
|
||||
if (*p == '^') {
|
||||
negate = true;
|
||||
p++;
|
||||
}
|
||||
|
||||
while (p < elem->classEnd) {
|
||||
if (p[1] == '-' && p + 2 < elem->classEnd) {
|
||||
char lo = *p, hi = p[2];
|
||||
char tc = c;
|
||||
if (ignoreCase) {
|
||||
if (lo >= 'A' && lo <= 'Z') lo += 32;
|
||||
if (hi >= 'A' && hi <= 'Z') hi += 32;
|
||||
if (tc >= 'A' && tc <= 'Z') tc += 32;
|
||||
}
|
||||
if (tc >= lo && tc <= hi) matched = true;
|
||||
p += 3;
|
||||
} else {
|
||||
char pc = *p;
|
||||
char tc = c;
|
||||
if (ignoreCase) {
|
||||
if (pc >= 'A' && pc <= 'Z') pc += 32;
|
||||
if (tc >= 'A' && tc <= 'Z') tc += 32;
|
||||
}
|
||||
if (pc == tc) matched = true;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
return negate ? !matched : matched;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char* parseElement(const char* pattern, PatternElement* elem) {
|
||||
if (*pattern == '.') {
|
||||
elem->type = ELEM_DOT;
|
||||
return pattern + 1;
|
||||
}
|
||||
|
||||
if (*pattern == '\\' && *(pattern + 1)) {
|
||||
elem->type = ELEM_ESCAPE;
|
||||
elem->literal = *(pattern + 1);
|
||||
return pattern + 2;
|
||||
}
|
||||
|
||||
if (*pattern == '[') {
|
||||
elem->type = ELEM_CLASS;
|
||||
elem->classStart = pattern + 1;
|
||||
const char* p = pattern + 1;
|
||||
if (*p == '^') p++;
|
||||
if (*p == ']') p++;
|
||||
while (*p && *p != ']') p++;
|
||||
elem->classEnd = p;
|
||||
return *p ? p + 1 : p;
|
||||
}
|
||||
|
||||
elem->type = ELEM_LITERAL;
|
||||
elem->literal = *pattern;
|
||||
return pattern + 1;
|
||||
}
|
||||
|
||||
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd);
|
||||
|
||||
static bool matchStar(PatternElement* elem, const char* pattern, const char* text,
|
||||
bool ignoreCase, const char** matchEnd) {
|
||||
const char* t = text;
|
||||
while (*t && matchElement(elem, *t, ignoreCase)) t++;
|
||||
do {
|
||||
if (matchHere(pattern, t, ignoreCase, matchEnd)) return true;
|
||||
} while (t-- > text);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchPlus(PatternElement* elem, const char* pattern, const char* text,
|
||||
bool ignoreCase, const char** matchEnd) {
|
||||
if (!*text || !matchElement(elem, *text, ignoreCase)) return false;
|
||||
return matchStar(elem, pattern, text + 1, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
static bool matchQuestion(PatternElement* elem, const char* pattern, const char* text,
|
||||
bool ignoreCase, const char** matchEnd) {
|
||||
if (matchHere(pattern, text, ignoreCase, matchEnd)) return true;
|
||||
if (*text && matchElement(elem, *text, ignoreCase)) {
|
||||
return matchHere(pattern, text + 1, ignoreCase, matchEnd);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchHere(const char* pattern, const char* text, bool ignoreCase, const char** matchEnd) {
|
||||
if (*pattern == '\0') {
|
||||
*matchEnd = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*pattern == '$' && *(pattern + 1) == '\0') {
|
||||
*matchEnd = text;
|
||||
return *text == '\0' || *text == '\n';
|
||||
}
|
||||
|
||||
PatternElement elem;
|
||||
const char* nextPattern = parseElement(pattern, &elem);
|
||||
|
||||
if (*nextPattern == '*') {
|
||||
return matchStar(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
if (*nextPattern == '+') {
|
||||
return matchPlus(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
if (*nextPattern == '?') {
|
||||
return matchQuestion(&elem, nextPattern + 1, text, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
if (matchElement(&elem, *text, ignoreCase)) {
|
||||
return matchHere(nextPattern, text + 1, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchPattern(const char* pattern, const char* text, bool ignoreCase,
|
||||
const char** matchStart, const char** matchEnd) {
|
||||
if (*pattern == '^') {
|
||||
*matchStart = text;
|
||||
return matchHere(pattern + 1, text, ignoreCase, matchEnd);
|
||||
}
|
||||
|
||||
do {
|
||||
*matchStart = text;
|
||||
if (matchHere(pattern, text, ignoreCase, matchEnd)) {
|
||||
return true;
|
||||
}
|
||||
} while (*text++);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void regexAllocate(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(RegexData));
|
||||
memset(data, 0, sizeof(RegexData));
|
||||
|
||||
const char* pattern = wrenGetSlotString(vm, 1);
|
||||
data->pattern = strdup(pattern);
|
||||
|
||||
const char* flags = "";
|
||||
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_STRING) {
|
||||
flags = wrenGetSlotString(vm, 2);
|
||||
}
|
||||
data->flags = strdup(flags);
|
||||
|
||||
for (const char* f = flags; *f; f++) {
|
||||
switch (*f) {
|
||||
case 'i': data->ignoreCase = true; break;
|
||||
case 'g': data->global = true; break;
|
||||
case 'm': data->multiline = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void regexFinalize(void* ptr) {
|
||||
RegexData* data = (RegexData*)ptr;
|
||||
if (data->pattern) free(data->pattern);
|
||||
if (data->flags) free(data->flags);
|
||||
}
|
||||
|
||||
void regexTest(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
bool result = matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd);
|
||||
wrenSetSlotBool(vm, 0, result);
|
||||
}
|
||||
|
||||
void regexMatch(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
|
||||
wrenSetSlotNull(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 7);
|
||||
wrenGetVariable(vm, "regex", "Match", 1);
|
||||
|
||||
size_t matchLen = matchEnd - matchStart;
|
||||
char* matchText = (char*)malloc(matchLen + 1);
|
||||
memcpy(matchText, matchStart, matchLen);
|
||||
matchText[matchLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 2, matchText);
|
||||
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
|
||||
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
|
||||
|
||||
wrenSetSlotNewList(vm, 5);
|
||||
wrenSetSlotString(vm, 6, matchText);
|
||||
wrenInsertInList(vm, 5, -1, 6);
|
||||
|
||||
free(matchText);
|
||||
|
||||
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
|
||||
wrenCall(vm, newMethod);
|
||||
wrenReleaseHandle(vm, newMethod);
|
||||
}
|
||||
|
||||
void regexMatchAll(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
const char* cursor = text;
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
wrenEnsureSlots(vm, 8);
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
|
||||
wrenGetVariable(vm, "regex", "Match", 1);
|
||||
|
||||
size_t matchLen = matchEnd - matchStart;
|
||||
char* matchText = (char*)malloc(matchLen + 1);
|
||||
memcpy(matchText, matchStart, matchLen);
|
||||
matchText[matchLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 2, matchText);
|
||||
wrenSetSlotDouble(vm, 3, (double)(matchStart - text));
|
||||
wrenSetSlotDouble(vm, 4, (double)(matchEnd - text));
|
||||
|
||||
wrenSetSlotNewList(vm, 5);
|
||||
wrenSetSlotString(vm, 6, matchText);
|
||||
wrenInsertInList(vm, 5, -1, 6);
|
||||
|
||||
free(matchText);
|
||||
|
||||
WrenHandle* newMethod = wrenMakeCallHandle(vm, "new_(_,_,_,_)");
|
||||
wrenCall(vm, newMethod);
|
||||
wrenReleaseHandle(vm, newMethod);
|
||||
|
||||
wrenInsertInList(vm, 0, -1, 1);
|
||||
|
||||
if (matchEnd == cursor) cursor++;
|
||||
else cursor = matchEnd;
|
||||
|
||||
if (*cursor == '\0') break;
|
||||
if (!data->global) break;
|
||||
}
|
||||
}
|
||||
|
||||
void regexReplace(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
const char* replacement = wrenGetSlotString(vm, 2);
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
if (!matchPattern(data->pattern, text, data->ignoreCase, &matchStart, &matchEnd)) {
|
||||
wrenSetSlotString(vm, 0, text);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t textLen = strlen(text);
|
||||
size_t replLen = strlen(replacement);
|
||||
size_t matchLen = matchEnd - matchStart;
|
||||
size_t resultLen = textLen - matchLen + replLen;
|
||||
|
||||
char* result = (char*)malloc(resultLen + 1);
|
||||
size_t prefixLen = matchStart - text;
|
||||
memcpy(result, text, prefixLen);
|
||||
memcpy(result + prefixLen, replacement, replLen);
|
||||
memcpy(result + prefixLen + replLen, matchEnd, textLen - prefixLen - matchLen);
|
||||
result[resultLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void regexReplaceAll(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
const char* replacement = wrenGetSlotString(vm, 2);
|
||||
|
||||
size_t textLen = strlen(text);
|
||||
size_t replLen = strlen(replacement);
|
||||
size_t capacity = textLen * 2 + 1;
|
||||
char* result = (char*)malloc(capacity);
|
||||
size_t resultLen = 0;
|
||||
|
||||
const char* cursor = text;
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
|
||||
size_t prefixLen = matchStart - cursor;
|
||||
|
||||
while (resultLen + prefixLen + replLen >= capacity) {
|
||||
capacity *= 2;
|
||||
result = (char*)realloc(result, capacity);
|
||||
}
|
||||
|
||||
memcpy(result + resultLen, cursor, prefixLen);
|
||||
resultLen += prefixLen;
|
||||
memcpy(result + resultLen, replacement, replLen);
|
||||
resultLen += replLen;
|
||||
|
||||
if (matchEnd == cursor) {
|
||||
if (*cursor) {
|
||||
result[resultLen++] = *cursor;
|
||||
}
|
||||
cursor++;
|
||||
} else {
|
||||
cursor = matchEnd;
|
||||
}
|
||||
|
||||
if (*cursor == '\0') break;
|
||||
}
|
||||
|
||||
size_t remaining = strlen(cursor);
|
||||
while (resultLen + remaining >= capacity) {
|
||||
capacity *= 2;
|
||||
result = (char*)realloc(result, capacity);
|
||||
}
|
||||
memcpy(result + resultLen, cursor, remaining);
|
||||
resultLen += remaining;
|
||||
result[resultLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void regexSplit(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
const char* cursor = text;
|
||||
const char* matchStart;
|
||||
const char* matchEnd;
|
||||
|
||||
while (matchPattern(data->pattern, cursor, data->ignoreCase, &matchStart, &matchEnd)) {
|
||||
size_t partLen = matchStart - cursor;
|
||||
char* part = (char*)malloc(partLen + 1);
|
||||
memcpy(part, cursor, partLen);
|
||||
part[partLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 2, part);
|
||||
wrenInsertInList(vm, 0, -1, 2);
|
||||
free(part);
|
||||
|
||||
if (matchEnd == cursor) cursor++;
|
||||
else cursor = matchEnd;
|
||||
|
||||
if (*cursor == '\0') break;
|
||||
}
|
||||
|
||||
wrenSetSlotString(vm, 2, cursor);
|
||||
wrenInsertInList(vm, 0, -1, 2);
|
||||
}
|
||||
|
||||
void regexPattern(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotString(vm, 0, data->pattern);
|
||||
}
|
||||
|
||||
void regexFlags(WrenVM* vm) {
|
||||
RegexData* data = (RegexData*)wrenGetSlotForeign(vm, 0);
|
||||
wrenSetSlotString(vm, 0, data->flags);
|
||||
}
|
||||
19
src/module/regex.h
Normal file
19
src/module/regex.h
Normal file
@ -0,0 +1,19 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_regex_h
|
||||
#define wren_regex_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void regexAllocate(WrenVM* vm);
|
||||
void regexFinalize(void* data);
|
||||
void regexTest(WrenVM* vm);
|
||||
void regexMatch(WrenVM* vm);
|
||||
void regexMatchAll(WrenVM* vm);
|
||||
void regexReplace(WrenVM* vm);
|
||||
void regexReplaceAll(WrenVM* vm);
|
||||
void regexSplit(WrenVM* vm);
|
||||
void regexPattern(WrenVM* vm);
|
||||
void regexFlags(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
36
src/module/regex.wren
vendored
Normal file
36
src/module/regex.wren
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
foreign class Regex {
|
||||
construct new(pattern) {}
|
||||
construct new(pattern, flags) {}
|
||||
|
||||
foreign test(string)
|
||||
foreign match(string)
|
||||
foreign matchAll(string)
|
||||
foreign replace(string, replacement)
|
||||
foreign replaceAll(string, replacement)
|
||||
foreign split(string)
|
||||
foreign pattern
|
||||
foreign flags
|
||||
}
|
||||
|
||||
class Match {
|
||||
construct new_(text, start, end, groups) {
|
||||
_text = text
|
||||
_start = start
|
||||
_end = end
|
||||
_groups = groups
|
||||
}
|
||||
|
||||
text { _text }
|
||||
start { _start }
|
||||
end { _end }
|
||||
groups { _groups }
|
||||
|
||||
group(index) {
|
||||
if (index < 0 || index >= _groups.count) return null
|
||||
return _groups[index]
|
||||
}
|
||||
|
||||
toString { _text }
|
||||
}
|
||||
40
src/module/regex.wren.inc
Normal file
40
src/module/regex.wren.inc
Normal file
@ -0,0 +1,40 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/regex.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* regexModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"foreign class Regex {\n"
|
||||
" construct new(pattern) {}\n"
|
||||
" construct new(pattern, flags) {}\n"
|
||||
"\n"
|
||||
" foreign test(string)\n"
|
||||
" foreign match(string)\n"
|
||||
" foreign matchAll(string)\n"
|
||||
" foreign replace(string, replacement)\n"
|
||||
" foreign replaceAll(string, replacement)\n"
|
||||
" foreign split(string)\n"
|
||||
" foreign pattern\n"
|
||||
" foreign flags\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Match {\n"
|
||||
" construct new_(text, start, end, groups) {\n"
|
||||
" _text = text\n"
|
||||
" _start = start\n"
|
||||
" _end = end\n"
|
||||
" _groups = groups\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" text { _text }\n"
|
||||
" start { _start }\n"
|
||||
" end { _end }\n"
|
||||
" groups { _groups }\n"
|
||||
"\n"
|
||||
" group(index) {\n"
|
||||
" if (index < 0 || index >= _groups.count) return null\n"
|
||||
" return _groups[index]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toString { _text }\n"
|
||||
"}\n";
|
||||
35
src/module/signal.wren
vendored
Normal file
35
src/module/signal.wren
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Signal {
|
||||
static SIGHUP { 1 }
|
||||
static SIGINT { 2 }
|
||||
static SIGQUIT { 3 }
|
||||
static SIGTERM { 15 }
|
||||
static SIGUSR1 { 10 }
|
||||
static SIGUSR2 { 12 }
|
||||
|
||||
foreign static trap_(signum, fiber)
|
||||
foreign static ignore_(signum)
|
||||
foreign static reset_(signum)
|
||||
|
||||
static trap(signum, fn) {
|
||||
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
|
||||
var fiber = Fiber.new {
|
||||
while (true) {
|
||||
fn.call()
|
||||
Fiber.yield()
|
||||
}
|
||||
}
|
||||
trap_(signum, fiber)
|
||||
}
|
||||
|
||||
static ignore(signum) {
|
||||
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
|
||||
ignore_(signum)
|
||||
}
|
||||
|
||||
static reset(signum) {
|
||||
if (!(signum is Num)) Fiber.abort("Signal number must be a number.")
|
||||
reset_(signum)
|
||||
}
|
||||
}
|
||||
39
src/module/signal.wren.inc
Normal file
39
src/module/signal.wren.inc
Normal file
@ -0,0 +1,39 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/signal.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* signalModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Signal {\n"
|
||||
" static SIGHUP { 1 }\n"
|
||||
" static SIGINT { 2 }\n"
|
||||
" static SIGQUIT { 3 }\n"
|
||||
" static SIGTERM { 15 }\n"
|
||||
" static SIGUSR1 { 10 }\n"
|
||||
" static SIGUSR2 { 12 }\n"
|
||||
"\n"
|
||||
" foreign static trap_(signum, fiber)\n"
|
||||
" foreign static ignore_(signum)\n"
|
||||
" foreign static reset_(signum)\n"
|
||||
"\n"
|
||||
" static trap(signum, fn) {\n"
|
||||
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
|
||||
" var fiber = Fiber.new {\n"
|
||||
" while (true) {\n"
|
||||
" fn.call()\n"
|
||||
" Fiber.yield()\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" trap_(signum, fiber)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static ignore(signum) {\n"
|
||||
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
|
||||
" ignore_(signum)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static reset(signum) {\n"
|
||||
" if (!(signum is Num)) Fiber.abort(\"Signal number must be a number.\")\n"
|
||||
" reset_(signum)\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
128
src/module/signal_module.c
Normal file
128
src/module/signal_module.c
Normal file
@ -0,0 +1,128 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "signal_module.h"
|
||||
#include "scheduler.h"
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
#include "uv.h"
|
||||
|
||||
#define MAX_SIGNALS 32
|
||||
|
||||
static uv_signal_t* signalHandles[MAX_SIGNALS] = {NULL};
|
||||
static WrenHandle* signalFibers[MAX_SIGNALS] = {NULL};
|
||||
|
||||
static void signalCallback(uv_signal_t* handle, int signum) {
|
||||
if (signum < 0 || signum >= MAX_SIGNALS) return;
|
||||
|
||||
WrenHandle* fiber = signalFibers[signum];
|
||||
if (fiber == NULL) return;
|
||||
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
|
||||
void signalTrap(WrenVM* vm) {
|
||||
int signum = (int)wrenGetSlotDouble(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
if (signum < 0 || signum >= MAX_SIGNALS) {
|
||||
wrenSetSlotString(vm, 0, "Invalid signal number.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (signalHandles[signum] != NULL) {
|
||||
uv_signal_stop(signalHandles[signum]);
|
||||
uv_close((uv_handle_t*)signalHandles[signum], NULL);
|
||||
free(signalHandles[signum]);
|
||||
}
|
||||
|
||||
if (signalFibers[signum] != NULL) {
|
||||
wrenReleaseHandle(vm, signalFibers[signum]);
|
||||
}
|
||||
|
||||
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
|
||||
if (handle == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_signal_init(getLoop(), handle);
|
||||
signalHandles[signum] = handle;
|
||||
signalFibers[signum] = fiber;
|
||||
|
||||
int result = uv_signal_start(handle, signalCallback, signum);
|
||||
if (result != 0) {
|
||||
free(handle);
|
||||
signalHandles[signum] = NULL;
|
||||
signalFibers[signum] = NULL;
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, uv_strerror(result));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void signalIgnore(WrenVM* vm) {
|
||||
int signum = (int)wrenGetSlotDouble(vm, 1);
|
||||
|
||||
if (signum < 0 || signum >= MAX_SIGNALS) {
|
||||
wrenSetSlotString(vm, 0, "Invalid signal number.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (signalHandles[signum] != NULL) {
|
||||
uv_signal_stop(signalHandles[signum]);
|
||||
uv_close((uv_handle_t*)signalHandles[signum], NULL);
|
||||
free(signalHandles[signum]);
|
||||
signalHandles[signum] = NULL;
|
||||
}
|
||||
|
||||
if (signalFibers[signum] != NULL) {
|
||||
wrenReleaseHandle(vm, signalFibers[signum]);
|
||||
signalFibers[signum] = NULL;
|
||||
}
|
||||
|
||||
uv_signal_t* handle = (uv_signal_t*)malloc(sizeof(uv_signal_t));
|
||||
if (handle == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_signal_init(getLoop(), handle);
|
||||
signalHandles[signum] = handle;
|
||||
|
||||
int result = uv_signal_start(handle, NULL, signum);
|
||||
if (result != 0) {
|
||||
free(handle);
|
||||
signalHandles[signum] = NULL;
|
||||
wrenSetSlotString(vm, 0, uv_strerror(result));
|
||||
wrenAbortFiber(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void signalReset(WrenVM* vm) {
|
||||
int signum = (int)wrenGetSlotDouble(vm, 1);
|
||||
|
||||
if (signum < 0 || signum >= MAX_SIGNALS) {
|
||||
wrenSetSlotString(vm, 0, "Invalid signal number.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (signalHandles[signum] != NULL) {
|
||||
uv_signal_stop(signalHandles[signum]);
|
||||
uv_close((uv_handle_t*)signalHandles[signum], NULL);
|
||||
free(signalHandles[signum]);
|
||||
signalHandles[signum] = NULL;
|
||||
}
|
||||
|
||||
if (signalFibers[signum] != NULL) {
|
||||
wrenReleaseHandle(vm, signalFibers[signum]);
|
||||
signalFibers[signum] = NULL;
|
||||
}
|
||||
}
|
||||
12
src/module/signal_module.h
Normal file
12
src/module/signal_module.h
Normal file
@ -0,0 +1,12 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_signal_module_h
|
||||
#define wren_signal_module_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void signalTrap(WrenVM* vm);
|
||||
void signalIgnore(WrenVM* vm);
|
||||
void signalReset(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
14
src/module/sqlite.wren
vendored
Normal file
14
src/module/sqlite.wren
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
foreign class Database {
|
||||
construct open(path) {}
|
||||
construct memory() {}
|
||||
|
||||
foreign execute(sql)
|
||||
foreign execute(sql, params)
|
||||
foreign query(sql)
|
||||
foreign query(sql, params)
|
||||
foreign close()
|
||||
foreign lastInsertId
|
||||
foreign changes
|
||||
}
|
||||
18
src/module/sqlite.wren.inc
Normal file
18
src/module/sqlite.wren.inc
Normal file
@ -0,0 +1,18 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/sqlite.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* sqliteModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"foreign class Database {\n"
|
||||
" construct open(path) {}\n"
|
||||
" construct memory() {}\n"
|
||||
"\n"
|
||||
" foreign execute(sql)\n"
|
||||
" foreign execute(sql, params)\n"
|
||||
" foreign query(sql)\n"
|
||||
" foreign query(sql, params)\n"
|
||||
" foreign close()\n"
|
||||
" foreign lastInsertId\n"
|
||||
" foreign changes\n"
|
||||
"}\n";
|
||||
219
src/module/sqlite_module.c
Normal file
219
src/module/sqlite_module.c
Normal file
@ -0,0 +1,219 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sqlite_module.h"
|
||||
#include "wren.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
typedef struct {
|
||||
sqlite3* db;
|
||||
} SqliteData;
|
||||
|
||||
void sqliteAllocate(WrenVM* vm) {
|
||||
SqliteData* data = (SqliteData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(SqliteData));
|
||||
data->db = NULL;
|
||||
|
||||
const char* path = ":memory:";
|
||||
if (wrenGetSlotCount(vm) > 1 && wrenGetSlotType(vm, 1) == WREN_TYPE_STRING) {
|
||||
path = wrenGetSlotString(vm, 1);
|
||||
}
|
||||
|
||||
int result = sqlite3_open(path, &data->db);
|
||||
if (result != SQLITE_OK) {
|
||||
const char* err = sqlite3_errmsg(data->db);
|
||||
sqlite3_close(data->db);
|
||||
data->db = NULL;
|
||||
wrenSetSlotString(vm, 0, err);
|
||||
wrenAbortFiber(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void sqliteFinalize(void* ptr) {
|
||||
SqliteData* data = (SqliteData*)ptr;
|
||||
if (data->db != NULL) {
|
||||
sqlite3_close(data->db);
|
||||
data->db = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void bindParams(WrenVM* vm, sqlite3_stmt* stmt, int paramsSlot) {
|
||||
int count = wrenGetListCount(vm, paramsSlot);
|
||||
wrenEnsureSlots(vm, paramsSlot + 2);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
wrenGetListElement(vm, paramsSlot, i, paramsSlot + 1);
|
||||
WrenType type = wrenGetSlotType(vm, paramsSlot + 1);
|
||||
|
||||
switch (type) {
|
||||
case WREN_TYPE_NULL:
|
||||
sqlite3_bind_null(stmt, i + 1);
|
||||
break;
|
||||
case WREN_TYPE_BOOL:
|
||||
sqlite3_bind_int(stmt, i + 1, wrenGetSlotBool(vm, paramsSlot + 1) ? 1 : 0);
|
||||
break;
|
||||
case WREN_TYPE_NUM:
|
||||
sqlite3_bind_double(stmt, i + 1, wrenGetSlotDouble(vm, paramsSlot + 1));
|
||||
break;
|
||||
case WREN_TYPE_STRING:
|
||||
sqlite3_bind_text(stmt, i + 1, wrenGetSlotString(vm, paramsSlot + 1), -1, SQLITE_TRANSIENT);
|
||||
break;
|
||||
default:
|
||||
sqlite3_bind_null(stmt, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void executeInternal(WrenVM* vm, bool hasParams) {
|
||||
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->db == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Database is closed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* sql = wrenGetSlotString(vm, 1);
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
|
||||
if (result != SQLITE_OK) {
|
||||
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasParams) {
|
||||
bindParams(vm, stmt, 2);
|
||||
}
|
||||
|
||||
result = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (result != SQLITE_DONE && result != SQLITE_ROW) {
|
||||
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotNull(vm, 0);
|
||||
}
|
||||
|
||||
void sqliteExecute(WrenVM* vm) {
|
||||
executeInternal(vm, false);
|
||||
}
|
||||
|
||||
void sqliteExecuteParams(WrenVM* vm) {
|
||||
executeInternal(vm, true);
|
||||
}
|
||||
|
||||
static void queryInternal(WrenVM* vm, bool hasParams) {
|
||||
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->db == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Database is closed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* sql = wrenGetSlotString(vm, 1);
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
int result = sqlite3_prepare_v2(data->db, sql, -1, &stmt, NULL);
|
||||
if (result != SQLITE_OK) {
|
||||
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasParams) {
|
||||
bindParams(vm, stmt, 2);
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 5);
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
int colCount = sqlite3_column_count(stmt);
|
||||
|
||||
while ((result = sqlite3_step(stmt)) == SQLITE_ROW) {
|
||||
wrenSetSlotNewMap(vm, 3);
|
||||
|
||||
for (int i = 0; i < colCount; i++) {
|
||||
const char* colName = sqlite3_column_name(stmt, i);
|
||||
wrenSetSlotString(vm, 1, colName);
|
||||
|
||||
int colType = sqlite3_column_type(stmt, i);
|
||||
switch (colType) {
|
||||
case SQLITE_NULL:
|
||||
wrenSetSlotNull(vm, 4);
|
||||
break;
|
||||
case SQLITE_INTEGER:
|
||||
wrenSetSlotDouble(vm, 4, (double)sqlite3_column_int64(stmt, i));
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
wrenSetSlotDouble(vm, 4, sqlite3_column_double(stmt, i));
|
||||
break;
|
||||
case SQLITE_TEXT:
|
||||
wrenSetSlotString(vm, 4, (const char*)sqlite3_column_text(stmt, i));
|
||||
break;
|
||||
case SQLITE_BLOB: {
|
||||
int size = sqlite3_column_bytes(stmt, i);
|
||||
const unsigned char* blob = sqlite3_column_blob(stmt, i);
|
||||
wrenSetSlotNewList(vm, 4);
|
||||
for (int j = 0; j < size; j++) {
|
||||
wrenSetSlotDouble(vm, 2, (double)blob[j]);
|
||||
wrenInsertInList(vm, 4, -1, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
wrenSetSlotNull(vm, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
wrenSetMapValue(vm, 3, 1, 4);
|
||||
}
|
||||
|
||||
wrenInsertInList(vm, 0, -1, 3);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (result != SQLITE_DONE) {
|
||||
wrenSetSlotString(vm, 0, sqlite3_errmsg(data->db));
|
||||
wrenAbortFiber(vm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void sqliteQuery(WrenVM* vm) {
|
||||
queryInternal(vm, false);
|
||||
}
|
||||
|
||||
void sqliteQueryParams(WrenVM* vm) {
|
||||
queryInternal(vm, true);
|
||||
}
|
||||
|
||||
void sqliteClose(WrenVM* vm) {
|
||||
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->db != NULL) {
|
||||
sqlite3_close(data->db);
|
||||
data->db = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void sqliteLastInsertId(WrenVM* vm) {
|
||||
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->db == NULL) {
|
||||
wrenSetSlotDouble(vm, 0, 0);
|
||||
return;
|
||||
}
|
||||
wrenSetSlotDouble(vm, 0, (double)sqlite3_last_insert_rowid(data->db));
|
||||
}
|
||||
|
||||
void sqliteChanges(WrenVM* vm) {
|
||||
SqliteData* data = (SqliteData*)wrenGetSlotForeign(vm, 0);
|
||||
if (data->db == NULL) {
|
||||
wrenSetSlotDouble(vm, 0, 0);
|
||||
return;
|
||||
}
|
||||
wrenSetSlotDouble(vm, 0, (double)sqlite3_changes(data->db));
|
||||
}
|
||||
18
src/module/sqlite_module.h
Normal file
18
src/module/sqlite_module.h
Normal file
@ -0,0 +1,18 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_sqlite_h
|
||||
#define wren_sqlite_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void sqliteAllocate(WrenVM* vm);
|
||||
void sqliteFinalize(void* data);
|
||||
void sqliteExecute(WrenVM* vm);
|
||||
void sqliteExecuteParams(WrenVM* vm);
|
||||
void sqliteQuery(WrenVM* vm);
|
||||
void sqliteQueryParams(WrenVM* vm);
|
||||
void sqliteClose(WrenVM* vm);
|
||||
void sqliteLastInsertId(WrenVM* vm);
|
||||
void sqliteChanges(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
184
src/module/subprocess.c
Normal file
184
src/module/subprocess.c
Normal file
@ -0,0 +1,184 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "subprocess.h"
|
||||
#include "scheduler.h"
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
#include "uv.h"
|
||||
|
||||
typedef struct {
|
||||
WrenHandle* fiber;
|
||||
uv_process_t process;
|
||||
uv_pipe_t stdoutPipe;
|
||||
uv_pipe_t stderrPipe;
|
||||
char* stdoutBuf;
|
||||
size_t stdoutLen;
|
||||
size_t stdoutCap;
|
||||
char* stderrBuf;
|
||||
size_t stderrLen;
|
||||
size_t stderrCap;
|
||||
int64_t exitCode;
|
||||
int handlesOpen;
|
||||
} ProcessData;
|
||||
|
||||
static void appendBuffer(char** buf, size_t* len, size_t* cap, const char* data, size_t dataLen) {
|
||||
if (*len + dataLen >= *cap) {
|
||||
size_t newCap = (*cap == 0) ? 1024 : *cap * 2;
|
||||
while (newCap < *len + dataLen + 1) newCap *= 2;
|
||||
*buf = (char*)realloc(*buf, newCap);
|
||||
*cap = newCap;
|
||||
}
|
||||
memcpy(*buf + *len, data, dataLen);
|
||||
*len += dataLen;
|
||||
(*buf)[*len] = '\0';
|
||||
}
|
||||
|
||||
static void onHandleClosed(uv_handle_t* handle) {
|
||||
ProcessData* data = (ProcessData*)handle->data;
|
||||
data->handlesOpen--;
|
||||
|
||||
if (data->handlesOpen > 0) return;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
wrenEnsureSlots(vm, 4);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
wrenSetSlotDouble(vm, 3, (double)data->exitCode);
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotString(vm, 3, data->stdoutBuf != NULL ? data->stdoutBuf : "");
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
wrenSetSlotString(vm, 3, data->stderrBuf != NULL ? data->stderrBuf : "");
|
||||
wrenInsertInList(vm, 2, -1, 3);
|
||||
|
||||
if (data->stdoutBuf != NULL) free(data->stdoutBuf);
|
||||
if (data->stderrBuf != NULL) free(data->stderrBuf);
|
||||
free(data);
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
static void allocBuffer(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
|
||||
buf->base = (char*)malloc(size);
|
||||
buf->len = size;
|
||||
}
|
||||
|
||||
static void onStdoutRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
ProcessData* data = (ProcessData*)stream->data;
|
||||
|
||||
if (nread > 0) {
|
||||
appendBuffer(&data->stdoutBuf, &data->stdoutLen, &data->stdoutCap, buf->base, nread);
|
||||
}
|
||||
|
||||
if (buf->base != NULL) free(buf->base);
|
||||
|
||||
if (nread < 0) {
|
||||
uv_close((uv_handle_t*)stream, onHandleClosed);
|
||||
}
|
||||
}
|
||||
|
||||
static void onStderrRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
ProcessData* data = (ProcessData*)stream->data;
|
||||
|
||||
if (nread > 0) {
|
||||
appendBuffer(&data->stderrBuf, &data->stderrLen, &data->stderrCap, buf->base, nread);
|
||||
}
|
||||
|
||||
if (buf->base != NULL) free(buf->base);
|
||||
|
||||
if (nread < 0) {
|
||||
uv_close((uv_handle_t*)stream, onHandleClosed);
|
||||
}
|
||||
}
|
||||
|
||||
static void onProcessExit(uv_process_t* process, int64_t exitStatus, int termSignal) {
|
||||
ProcessData* data = (ProcessData*)process->data;
|
||||
data->exitCode = exitStatus;
|
||||
uv_close((uv_handle_t*)process, onHandleClosed);
|
||||
}
|
||||
|
||||
void subprocessRun(WrenVM* vm) {
|
||||
int argCount = wrenGetListCount(vm, 1);
|
||||
if (argCount == 0) {
|
||||
wrenSetSlotString(vm, 0, "Command list cannot be empty.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
char** args = (char**)malloc((argCount + 1) * sizeof(char*));
|
||||
if (args == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 4);
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
wrenGetListElement(vm, 1, i, 3);
|
||||
const char* arg = wrenGetSlotString(vm, 3);
|
||||
args[i] = strdup(arg);
|
||||
}
|
||||
args[argCount] = NULL;
|
||||
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
ProcessData* data = (ProcessData*)calloc(1, sizeof(ProcessData));
|
||||
if (data == NULL) {
|
||||
for (int i = 0; i < argCount; i++) free(args[i]);
|
||||
free(args);
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fiber = fiber;
|
||||
data->handlesOpen = 3;
|
||||
|
||||
uv_loop_t* loop = getLoop();
|
||||
|
||||
uv_pipe_init(loop, &data->stdoutPipe, 0);
|
||||
uv_pipe_init(loop, &data->stderrPipe, 0);
|
||||
data->stdoutPipe.data = data;
|
||||
data->stderrPipe.data = data;
|
||||
data->process.data = data;
|
||||
|
||||
uv_stdio_container_t stdio[3];
|
||||
stdio[0].flags = UV_IGNORE;
|
||||
stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
|
||||
stdio[1].data.stream = (uv_stream_t*)&data->stdoutPipe;
|
||||
stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
|
||||
stdio[2].data.stream = (uv_stream_t*)&data->stderrPipe;
|
||||
|
||||
uv_process_options_t options;
|
||||
memset(&options, 0, sizeof(options));
|
||||
options.file = args[0];
|
||||
options.args = args;
|
||||
options.stdio = stdio;
|
||||
options.stdio_count = 3;
|
||||
options.exit_cb = onProcessExit;
|
||||
|
||||
int result = uv_spawn(loop, &data->process, &options);
|
||||
|
||||
for (int i = 0; i < argCount; i++) free(args[i]);
|
||||
free(args);
|
||||
|
||||
if (result != 0) {
|
||||
data->handlesOpen = 2;
|
||||
uv_close((uv_handle_t*)&data->stdoutPipe, onHandleClosed);
|
||||
uv_close((uv_handle_t*)&data->stderrPipe, onHandleClosed);
|
||||
wrenReleaseHandle(vm, fiber);
|
||||
wrenSetSlotString(vm, 0, uv_strerror(result));
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_read_start((uv_stream_t*)&data->stdoutPipe, allocBuffer, onStdoutRead);
|
||||
uv_read_start((uv_stream_t*)&data->stderrPipe, allocBuffer, onStderrRead);
|
||||
}
|
||||
10
src/module/subprocess.h
Normal file
10
src/module/subprocess.h
Normal file
@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_subprocess_h
|
||||
#define wren_subprocess_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void subprocessRun(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
47
src/module/subprocess.wren
vendored
Normal file
47
src/module/subprocess.wren
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class ProcessResult {
|
||||
construct new_(exitCode, stdout, stderr) {
|
||||
_exitCode = exitCode
|
||||
_stdout = stdout
|
||||
_stderr = stderr
|
||||
}
|
||||
|
||||
exitCode { _exitCode }
|
||||
stdout { _stdout }
|
||||
stderr { _stderr }
|
||||
success { _exitCode == 0 }
|
||||
|
||||
toString {
|
||||
if (success) return _stdout
|
||||
return "Process exited with code %(_exitCode): %(_stderr)"
|
||||
}
|
||||
}
|
||||
|
||||
class Subprocess {
|
||||
foreign static run_(args, fiber)
|
||||
|
||||
static run(command) {
|
||||
if (command is String) {
|
||||
return run(["/bin/sh", "-c", command])
|
||||
}
|
||||
if (!(command is List)) Fiber.abort("Command must be a String or List.")
|
||||
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
|
||||
run_(command, Fiber.current)
|
||||
var result = Scheduler.runNextScheduled_()
|
||||
return ProcessResult.new_(result[0], result[1], result[2])
|
||||
}
|
||||
|
||||
static run(command, cwd) {
|
||||
if (command is String) {
|
||||
return run(["/bin/sh", "-c", command], cwd)
|
||||
}
|
||||
if (!(command is List)) Fiber.abort("Command must be a String or List.")
|
||||
if (command.count == 0) Fiber.abort("Command list cannot be empty.")
|
||||
run_(command, Fiber.current)
|
||||
var result = Scheduler.runNextScheduled_()
|
||||
return ProcessResult.new_(result[0], result[1], result[2])
|
||||
}
|
||||
}
|
||||
51
src/module/subprocess.wren.inc
Normal file
51
src/module/subprocess.wren.inc
Normal file
@ -0,0 +1,51 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/subprocess.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* subprocessModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class ProcessResult {\n"
|
||||
" construct new_(exitCode, stdout, stderr) {\n"
|
||||
" _exitCode = exitCode\n"
|
||||
" _stdout = stdout\n"
|
||||
" _stderr = stderr\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" exitCode { _exitCode }\n"
|
||||
" stdout { _stdout }\n"
|
||||
" stderr { _stderr }\n"
|
||||
" success { _exitCode == 0 }\n"
|
||||
"\n"
|
||||
" toString {\n"
|
||||
" if (success) return _stdout\n"
|
||||
" return \"Process exited with code %(_exitCode): %(_stderr)\"\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Subprocess {\n"
|
||||
" foreign static run_(args, fiber)\n"
|
||||
"\n"
|
||||
" static run(command) {\n"
|
||||
" if (command is String) {\n"
|
||||
" return run([\"/bin/sh\", \"-c\", command])\n"
|
||||
" }\n"
|
||||
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
|
||||
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
|
||||
" run_(command, Fiber.current)\n"
|
||||
" var result = Scheduler.runNextScheduled_()\n"
|
||||
" return ProcessResult.new_(result[0], result[1], result[2])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static run(command, cwd) {\n"
|
||||
" if (command is String) {\n"
|
||||
" return run([\"/bin/sh\", \"-c\", command], cwd)\n"
|
||||
" }\n"
|
||||
" if (!(command is List)) Fiber.abort(\"Command must be a String or List.\")\n"
|
||||
" if (command.count == 0) Fiber.abort(\"Command list cannot be empty.\")\n"
|
||||
" run_(command, Fiber.current)\n"
|
||||
" var result = Scheduler.runNextScheduled_()\n"
|
||||
" return ProcessResult.new_(result[0], result[1], result[2])\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
403
src/module/tls.c
Normal file
403
src/module/tls.c
Normal file
@ -0,0 +1,403 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include "uv.h"
|
||||
#include "vm.h"
|
||||
#include "scheduler.h"
|
||||
#include "tls.h"
|
||||
|
||||
static SSL_CTX* globalCtx = NULL;
|
||||
static bool sslInitialized = false;
|
||||
|
||||
typedef enum {
|
||||
TLS_STATE_DISCONNECTED,
|
||||
TLS_STATE_CONNECTING,
|
||||
TLS_STATE_HANDSHAKING,
|
||||
TLS_STATE_CONNECTED,
|
||||
TLS_STATE_ERROR
|
||||
} TlsState;
|
||||
|
||||
typedef struct {
|
||||
uv_tcp_t* handle;
|
||||
SSL* ssl;
|
||||
BIO* readBio;
|
||||
BIO* writeBio;
|
||||
WrenHandle* fiber;
|
||||
TlsState state;
|
||||
char* hostname;
|
||||
char* readBuffer;
|
||||
size_t readBufferSize;
|
||||
bool wantRead;
|
||||
bool writeInProgress;
|
||||
} TlsSocketData;
|
||||
|
||||
static void initOpenSSL() {
|
||||
if (sslInitialized) return;
|
||||
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
globalCtx = SSL_CTX_new(TLS_client_method());
|
||||
if (globalCtx) {
|
||||
SSL_CTX_set_default_verify_paths(globalCtx);
|
||||
SSL_CTX_set_verify(globalCtx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_mode(globalCtx, SSL_MODE_AUTO_RETRY);
|
||||
}
|
||||
|
||||
sslInitialized = true;
|
||||
}
|
||||
|
||||
static void tlsCloseCallback(uv_handle_t* handle) {
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void flushWriteBio(TlsSocketData* data);
|
||||
static void doHandshake(TlsSocketData* data);
|
||||
|
||||
void tlsSocketAllocate(WrenVM* vm) {
|
||||
initOpenSSL();
|
||||
|
||||
TlsSocketData* data = (TlsSocketData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(TlsSocketData));
|
||||
memset(data, 0, sizeof(TlsSocketData));
|
||||
|
||||
data->handle = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(getLoop(), data->handle);
|
||||
data->handle->data = data;
|
||||
|
||||
data->ssl = SSL_new(globalCtx);
|
||||
data->readBio = BIO_new(BIO_s_mem());
|
||||
data->writeBio = BIO_new(BIO_s_mem());
|
||||
SSL_set_bio(data->ssl, data->readBio, data->writeBio);
|
||||
SSL_set_connect_state(data->ssl);
|
||||
|
||||
data->state = TLS_STATE_DISCONNECTED;
|
||||
data->readBufferSize = 16384;
|
||||
data->readBuffer = (char*)malloc(data->readBufferSize);
|
||||
}
|
||||
|
||||
void tlsSocketFinalize(void* ptr) {
|
||||
TlsSocketData* data = (TlsSocketData*)ptr;
|
||||
|
||||
if (data->ssl) {
|
||||
SSL_free(data->ssl);
|
||||
data->ssl = NULL;
|
||||
}
|
||||
|
||||
if (data->handle) {
|
||||
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
|
||||
data->handle = NULL;
|
||||
}
|
||||
|
||||
if (data->hostname) {
|
||||
free(data->hostname);
|
||||
data->hostname = NULL;
|
||||
}
|
||||
|
||||
if (data->readBuffer) {
|
||||
free(data->readBuffer);
|
||||
data->readBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void tlsAllocCallback(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) {
|
||||
buf->base = (char*)malloc(suggestedSize);
|
||||
buf->len = suggestedSize;
|
||||
}
|
||||
|
||||
static void tlsReadCallback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
||||
TlsSocketData* data = (TlsSocketData*)stream->data;
|
||||
|
||||
if (nread < 0) {
|
||||
uv_read_stop(stream);
|
||||
free(buf->base);
|
||||
|
||||
if (data->fiber) {
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
if (nread == UV_EOF) {
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotNull(getVM(), 2);
|
||||
schedulerFinishResume();
|
||||
} else {
|
||||
schedulerResumeError(fiber, uv_strerror(nread));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread > 0) {
|
||||
BIO_write(data->readBio, buf->base, nread);
|
||||
}
|
||||
free(buf->base);
|
||||
|
||||
if (data->state == TLS_STATE_HANDSHAKING) {
|
||||
doHandshake(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->state == TLS_STATE_CONNECTED && data->wantRead && data->fiber) {
|
||||
int decrypted = SSL_read(data->ssl, data->readBuffer, data->readBufferSize - 1);
|
||||
|
||||
if (decrypted > 0) {
|
||||
uv_read_stop(stream);
|
||||
data->wantRead = false;
|
||||
flushWriteBio(data);
|
||||
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotBytes(getVM(), 2, data->readBuffer, decrypted);
|
||||
schedulerFinishResume();
|
||||
} else {
|
||||
int err = SSL_get_error(data->ssl, decrypted);
|
||||
if (err == SSL_ERROR_ZERO_RETURN) {
|
||||
uv_read_stop(stream);
|
||||
data->wantRead = false;
|
||||
flushWriteBio(data);
|
||||
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotNull(getVM(), 2);
|
||||
schedulerFinishResume();
|
||||
} else if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
uv_read_stop(stream);
|
||||
data->wantRead = false;
|
||||
flushWriteBio(data);
|
||||
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
schedulerResumeError(fiber, "SSL read error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uv_write_t req;
|
||||
uv_buf_t buf;
|
||||
TlsSocketData* data;
|
||||
bool isLast;
|
||||
} TlsWriteRequest;
|
||||
|
||||
static void tlsWriteCallback(uv_write_t* req, int status) {
|
||||
TlsWriteRequest* wr = (TlsWriteRequest*)req;
|
||||
TlsSocketData* data = wr->data;
|
||||
bool isLast = wr->isLast;
|
||||
|
||||
free(wr->buf.base);
|
||||
free(wr);
|
||||
|
||||
if (isLast && data->writeInProgress && data->fiber) {
|
||||
data->writeInProgress = false;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
if (status < 0) {
|
||||
schedulerResumeError(fiber, uv_strerror(status));
|
||||
} else {
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void flushWriteBio(TlsSocketData* data) {
|
||||
if (data->handle == NULL) return;
|
||||
|
||||
char buffer[16384];
|
||||
int pending;
|
||||
|
||||
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
|
||||
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
|
||||
wr->buf.base = (char*)malloc(pending);
|
||||
wr->buf.len = pending;
|
||||
memcpy(wr->buf.base, buffer, pending);
|
||||
wr->data = data;
|
||||
wr->isLast = false;
|
||||
wr->req.data = wr;
|
||||
|
||||
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
|
||||
}
|
||||
}
|
||||
|
||||
static void doHandshake(TlsSocketData* data) {
|
||||
int result = SSL_do_handshake(data->ssl);
|
||||
flushWriteBio(data);
|
||||
|
||||
if (result == 1) {
|
||||
data->state = TLS_STATE_CONNECTED;
|
||||
uv_read_stop((uv_stream_t*)data->handle);
|
||||
|
||||
if (data->fiber) {
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
} else {
|
||||
int err = SSL_get_error(data->ssl, result);
|
||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->state = TLS_STATE_ERROR;
|
||||
uv_read_stop((uv_stream_t*)data->handle);
|
||||
|
||||
if (data->fiber) {
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
|
||||
unsigned long errCode = ERR_get_error();
|
||||
char errMsg[256];
|
||||
ERR_error_string_n(errCode, errMsg, sizeof(errMsg));
|
||||
schedulerResumeError(fiber, errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tlsConnectCallback(uv_connect_t* req, int status) {
|
||||
TlsSocketData* data = (TlsSocketData*)req->data;
|
||||
free(req);
|
||||
|
||||
if (status < 0) {
|
||||
data->state = TLS_STATE_ERROR;
|
||||
if (data->fiber) {
|
||||
WrenHandle* fiber = data->fiber;
|
||||
data->fiber = NULL;
|
||||
schedulerResumeError(fiber, uv_strerror(status));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
data->state = TLS_STATE_HANDSHAKING;
|
||||
|
||||
if (data->hostname) {
|
||||
SSL_set_tlsext_host_name(data->ssl, data->hostname);
|
||||
SSL_set1_host(data->ssl, data->hostname);
|
||||
}
|
||||
|
||||
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
|
||||
|
||||
doHandshake(data);
|
||||
}
|
||||
|
||||
void tlsSocketConnect(WrenVM* vm) {
|
||||
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
|
||||
const char* host = wrenGetSlotString(vm, 1);
|
||||
int port = (int)wrenGetSlotDouble(vm, 2);
|
||||
const char* hostname = wrenGetSlotString(vm, 3);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 4);
|
||||
|
||||
data->fiber = fiber;
|
||||
data->hostname = strdup(hostname);
|
||||
data->state = TLS_STATE_CONNECTING;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
int r = uv_ip4_addr(host, port, &addr);
|
||||
if (r != 0) {
|
||||
data->state = TLS_STATE_ERROR;
|
||||
schedulerResumeError(fiber, "Invalid IP address");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_connect_t* connectReq = (uv_connect_t*)malloc(sizeof(uv_connect_t));
|
||||
connectReq->data = data;
|
||||
|
||||
r = uv_tcp_connect(connectReq, data->handle, (const struct sockaddr*)&addr, tlsConnectCallback);
|
||||
if (r != 0) {
|
||||
free(connectReq);
|
||||
data->state = TLS_STATE_ERROR;
|
||||
schedulerResumeError(fiber, uv_strerror(r));
|
||||
}
|
||||
}
|
||||
|
||||
void tlsSocketWrite(WrenVM* vm) {
|
||||
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
|
||||
int length;
|
||||
const char* text = wrenGetSlotBytes(vm, 1, &length);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
|
||||
if (data->state != TLS_STATE_CONNECTED) {
|
||||
schedulerResumeError(fiber, "Socket not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
data->fiber = fiber;
|
||||
data->writeInProgress = true;
|
||||
|
||||
int written = SSL_write(data->ssl, text, length);
|
||||
if (written <= 0) {
|
||||
int err = SSL_get_error(data->ssl, written);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
data->writeInProgress = false;
|
||||
data->fiber = NULL;
|
||||
schedulerResumeError(fiber, "SSL write error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[16384];
|
||||
int pending;
|
||||
TlsWriteRequest* lastWr = NULL;
|
||||
|
||||
while ((pending = BIO_read(data->writeBio, buffer, sizeof(buffer))) > 0) {
|
||||
TlsWriteRequest* wr = (TlsWriteRequest*)malloc(sizeof(TlsWriteRequest));
|
||||
wr->buf.base = (char*)malloc(pending);
|
||||
wr->buf.len = pending;
|
||||
memcpy(wr->buf.base, buffer, pending);
|
||||
wr->data = data;
|
||||
wr->isLast = false;
|
||||
wr->req.data = wr;
|
||||
lastWr = wr;
|
||||
|
||||
uv_write(&wr->req, (uv_stream_t*)data->handle, &wr->buf, 1, tlsWriteCallback);
|
||||
}
|
||||
|
||||
if (lastWr) {
|
||||
lastWr->isLast = true;
|
||||
} else {
|
||||
data->writeInProgress = false;
|
||||
data->fiber = NULL;
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
}
|
||||
|
||||
void tlsSocketRead(WrenVM* vm) {
|
||||
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 1);
|
||||
|
||||
if (data->state != TLS_STATE_CONNECTED) {
|
||||
schedulerResumeError(fiber, "Socket not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
data->fiber = fiber;
|
||||
data->wantRead = true;
|
||||
uv_read_start((uv_stream_t*)data->handle, tlsAllocCallback, tlsReadCallback);
|
||||
}
|
||||
|
||||
void tlsSocketClose(WrenVM* vm) {
|
||||
TlsSocketData* data = (TlsSocketData*)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
if (data->ssl) {
|
||||
SSL_shutdown(data->ssl);
|
||||
}
|
||||
|
||||
if (data->handle && !uv_is_closing((uv_handle_t*)data->handle)) {
|
||||
uv_close((uv_handle_t*)data->handle, tlsCloseCallback);
|
||||
data->handle = NULL;
|
||||
}
|
||||
|
||||
data->state = TLS_STATE_DISCONNECTED;
|
||||
}
|
||||
15
src/module/tls.h
Normal file
15
src/module/tls.h
Normal file
@ -0,0 +1,15 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef tls_h
|
||||
#define tls_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void tlsSocketAllocate(WrenVM* vm);
|
||||
void tlsSocketFinalize(void* data);
|
||||
void tlsSocketConnect(WrenVM* vm);
|
||||
void tlsSocketWrite(WrenVM* vm);
|
||||
void tlsSocketRead(WrenVM* vm);
|
||||
void tlsSocketClose(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
30
src/module/tls.wren
vendored
Normal file
30
src/module/tls.wren
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
foreign class TlsSocket {
|
||||
construct new() {}
|
||||
|
||||
foreign connect_(host, port, hostname, fiber)
|
||||
foreign write_(text, fiber)
|
||||
foreign read_(fiber)
|
||||
foreign close_()
|
||||
|
||||
static connect(host, port, hostname) {
|
||||
var socket = TlsSocket.new()
|
||||
Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }
|
||||
return socket
|
||||
}
|
||||
|
||||
write(text) {
|
||||
Scheduler.await_ { write_(text, Fiber.current) }
|
||||
}
|
||||
|
||||
read() {
|
||||
return Scheduler.await_ { read_(Fiber.current) }
|
||||
}
|
||||
|
||||
close() {
|
||||
close_()
|
||||
}
|
||||
}
|
||||
34
src/module/tls.wren.inc
Normal file
34
src/module/tls.wren.inc
Normal file
@ -0,0 +1,34 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `/home/retoor/projects/wren-cli/src/module/tls.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* tlsModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"foreign class TlsSocket {\n"
|
||||
" construct new() {}\n"
|
||||
"\n"
|
||||
" foreign connect_(host, port, hostname, fiber)\n"
|
||||
" foreign write_(text, fiber)\n"
|
||||
" foreign read_(fiber)\n"
|
||||
" foreign close_()\n"
|
||||
"\n"
|
||||
" static connect(host, port, hostname) {\n"
|
||||
" var socket = TlsSocket.new()\n"
|
||||
" Scheduler.await_ { socket.connect_(host, port, hostname, Fiber.current) }\n"
|
||||
" return socket\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" write(text) {\n"
|
||||
" Scheduler.await_ { write_(text, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" read() {\n"
|
||||
" return Scheduler.await_ { read_(Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" close() {\n"
|
||||
" close_()\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
553
src/module/websocket.wren
vendored
Normal file
553
src/module/websocket.wren
vendored
Normal file
@ -0,0 +1,553 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "net" for Socket, Server
|
||||
import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
import "crypto" for Crypto, Hash
|
||||
import "base64" for Base64
|
||||
|
||||
class WebSocketMessage {
|
||||
construct new_(opcode, payload, fin) {
|
||||
_opcode = opcode
|
||||
_payload = payload
|
||||
_fin = fin
|
||||
}
|
||||
|
||||
opcode { _opcode }
|
||||
payload { _payload }
|
||||
fin { _fin }
|
||||
|
||||
isText { _opcode == 1 }
|
||||
isBinary { _opcode == 2 }
|
||||
isClose { _opcode == 8 }
|
||||
isPing { _opcode == 9 }
|
||||
isPong { _opcode == 10 }
|
||||
|
||||
text {
|
||||
if (!isText) Fiber.abort("Message is not a text frame.")
|
||||
return bytesToString_(_payload)
|
||||
}
|
||||
|
||||
bytes { _payload }
|
||||
|
||||
closeCode {
|
||||
if (!isClose) Fiber.abort("Message is not a close frame.")
|
||||
if (_payload.count < 2) return 1005
|
||||
return (_payload[0] << 8) | _payload[1]
|
||||
}
|
||||
|
||||
closeReason {
|
||||
if (!isClose) Fiber.abort("Message is not a close frame.")
|
||||
if (_payload.count <= 2) return ""
|
||||
return bytesToString_(_payload[2..-1])
|
||||
}
|
||||
|
||||
static bytesToString_(bytes) {
|
||||
var str = ""
|
||||
for (b in bytes) {
|
||||
str = str + String.fromCodePoint(b)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }
|
||||
}
|
||||
|
||||
class WebSocket {
|
||||
construct new_(socket, url, isClient) {
|
||||
_socket = socket
|
||||
_url = url
|
||||
_isClient = isClient
|
||||
_isOpen = true
|
||||
_fragmentBuffer = []
|
||||
_fragmentOpcode = null
|
||||
_readBuffer = []
|
||||
}
|
||||
|
||||
static connect(url) { connect(url, {}) }
|
||||
|
||||
static connect(url, headers) {
|
||||
var parsed = parseUrl_(url)
|
||||
|
||||
if (parsed["scheme"] != "ws" && parsed["scheme"] != "wss") {
|
||||
Fiber.abort("Unsupported scheme: %(parsed["scheme"]). Use ws:// or wss://")
|
||||
}
|
||||
|
||||
var isSecure = parsed["scheme"] == "wss"
|
||||
var host = parsed["host"]
|
||||
var port = parsed["port"]
|
||||
var path = parsed["path"]
|
||||
|
||||
var addresses = Dns.lookup(host, 4)
|
||||
if (addresses.count == 0) {
|
||||
Fiber.abort("Could not resolve host: %(host)")
|
||||
}
|
||||
|
||||
var socket
|
||||
if (isSecure) {
|
||||
socket = TlsSocket.connect(addresses[0], port, host)
|
||||
} else {
|
||||
socket = Socket.connect(addresses[0], port)
|
||||
}
|
||||
|
||||
var keyBytes = Crypto.randomBytes(16)
|
||||
var key = Base64.encode(bytesToString_(keyBytes))
|
||||
|
||||
var request = "GET %(path) HTTP/1.1\r\n"
|
||||
request = request + "Host: %(host)\r\n"
|
||||
request = request + "Upgrade: websocket\r\n"
|
||||
request = request + "Connection: Upgrade\r\n"
|
||||
request = request + "Sec-WebSocket-Key: %(key)\r\n"
|
||||
request = request + "Sec-WebSocket-Version: 13\r\n"
|
||||
|
||||
for (entry in headers) {
|
||||
request = request + "%(entry.key): %(entry.value)\r\n"
|
||||
}
|
||||
request = request + "\r\n"
|
||||
|
||||
socket.write(request)
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
Fiber.abort("Connection closed during handshake")
|
||||
}
|
||||
response = response + chunk
|
||||
if (response.indexOf("\r\n\r\n") >= 0) break
|
||||
}
|
||||
|
||||
var headerEnd = response.indexOf("\r\n\r\n")
|
||||
var headerPart = response[0...headerEnd]
|
||||
var lines = splitLines_(headerPart)
|
||||
|
||||
if (lines.count == 0) {
|
||||
Fiber.abort("Invalid WebSocket handshake response")
|
||||
}
|
||||
|
||||
var statusLine = lines[0]
|
||||
if (!statusLine.contains("101")) {
|
||||
Fiber.abort("WebSocket handshake failed: %(statusLine)")
|
||||
}
|
||||
|
||||
var expectedAccept = computeAcceptKey_(key)
|
||||
var receivedAccept = null
|
||||
|
||||
for (i in 1...lines.count) {
|
||||
var line = lines[i]
|
||||
var colonPos = line.indexOf(":")
|
||||
if (colonPos > 0) {
|
||||
var name = toLower_(line[0...colonPos].trim())
|
||||
var value = line[(colonPos + 1)..-1].trim()
|
||||
if (name == "sec-websocket-accept") {
|
||||
receivedAccept = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (receivedAccept != expectedAccept) {
|
||||
Fiber.abort("Invalid Sec-WebSocket-Accept header")
|
||||
}
|
||||
|
||||
return WebSocket.new_(socket, url, true)
|
||||
}
|
||||
|
||||
static parseUrl_(url) {
|
||||
var result = {
|
||||
"scheme": "ws",
|
||||
"host": "",
|
||||
"port": 80,
|
||||
"path": "/"
|
||||
}
|
||||
|
||||
var rest = url
|
||||
|
||||
var schemeEnd = rest.indexOf("://")
|
||||
if (schemeEnd >= 0) {
|
||||
result["scheme"] = toLower_(rest[0...schemeEnd])
|
||||
rest = rest[(schemeEnd + 3)..-1]
|
||||
if (result["scheme"] == "wss") result["port"] = 443
|
||||
}
|
||||
|
||||
var pathStart = rest.indexOf("/")
|
||||
var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest
|
||||
result["path"] = pathStart >= 0 ? rest[pathStart..-1] : "/"
|
||||
|
||||
var portStart = hostPart.indexOf(":")
|
||||
if (portStart >= 0) {
|
||||
result["host"] = hostPart[0...portStart]
|
||||
result["port"] = Num.fromString(hostPart[(portStart + 1)..-1])
|
||||
} else {
|
||||
result["host"] = hostPart
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static splitLines_(text) {
|
||||
var lines = []
|
||||
var start = 0
|
||||
var i = 0
|
||||
while (i < text.count) {
|
||||
if (text[i] == "\r" && i + 1 < text.count && text[i + 1] == "\n") {
|
||||
lines.add(text[start...i])
|
||||
start = i + 2
|
||||
i = i + 2
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if (start < text.count) {
|
||||
lines.add(text[start..-1])
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
static toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static computeAcceptKey_(key) {
|
||||
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
var combined = key + magic
|
||||
var sha1 = Hash.sha1(combined)
|
||||
var sha1Str = bytesToString_(sha1)
|
||||
return Base64.encode(sha1Str)
|
||||
}
|
||||
|
||||
static bytesToString_(bytes) {
|
||||
var str = ""
|
||||
for (b in bytes) {
|
||||
str = str + String.fromByte(b)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
url { _url }
|
||||
isOpen { _isOpen }
|
||||
|
||||
send(text) {
|
||||
if (!_isOpen) Fiber.abort("WebSocket is not open.")
|
||||
if (!(text is String)) Fiber.abort("Data must be a string.")
|
||||
var payload = stringToBytes_(text)
|
||||
sendFrame_(1, payload)
|
||||
}
|
||||
|
||||
sendBinary(bytes) {
|
||||
if (!_isOpen) Fiber.abort("WebSocket is not open.")
|
||||
if (!(bytes is List)) Fiber.abort("Data must be a list of bytes.")
|
||||
sendFrame_(2, bytes)
|
||||
}
|
||||
|
||||
ping() { ping([]) }
|
||||
|
||||
ping(data) {
|
||||
if (!_isOpen) Fiber.abort("WebSocket is not open.")
|
||||
var payload = data
|
||||
if (data is String) payload = stringToBytes_(data)
|
||||
if (payload.count > 125) Fiber.abort("Ping payload too large (max 125 bytes).")
|
||||
sendFrame_(9, payload)
|
||||
}
|
||||
|
||||
pong(data) {
|
||||
if (!_isOpen) Fiber.abort("WebSocket is not open.")
|
||||
var payload = data
|
||||
if (data is String) payload = stringToBytes_(data)
|
||||
if (payload.count > 125) Fiber.abort("Pong payload too large (max 125 bytes).")
|
||||
sendFrame_(10, payload)
|
||||
}
|
||||
|
||||
close() { close(1000, "") }
|
||||
|
||||
close(code, reason) {
|
||||
if (!_isOpen) return
|
||||
|
||||
var payload = []
|
||||
payload.add((code >> 8) & 0xFF)
|
||||
payload.add(code & 0xFF)
|
||||
for (b in stringToBytes_(reason)) {
|
||||
payload.add(b)
|
||||
}
|
||||
|
||||
sendFrame_(8, payload)
|
||||
_isOpen = false
|
||||
_socket.close()
|
||||
}
|
||||
|
||||
receive() {
|
||||
if (!_isOpen) return null
|
||||
|
||||
while (true) {
|
||||
var frame = readFrame_()
|
||||
if (frame == null) {
|
||||
_isOpen = false
|
||||
return null
|
||||
}
|
||||
|
||||
var opcode = frame.opcode
|
||||
var payload = frame.payload
|
||||
var fin = frame.fin
|
||||
|
||||
if (opcode == 8) {
|
||||
_isOpen = false
|
||||
if (_isClient) {
|
||||
sendFrame_(8, payload)
|
||||
_socket.close()
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
if (opcode == 9) {
|
||||
sendFrame_(10, payload)
|
||||
continue
|
||||
}
|
||||
|
||||
if (opcode == 10) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (opcode == 0) {
|
||||
for (b in payload) {
|
||||
_fragmentBuffer.add(b)
|
||||
}
|
||||
if (fin) {
|
||||
var completePayload = _fragmentBuffer
|
||||
var completeOpcode = _fragmentOpcode
|
||||
_fragmentBuffer = []
|
||||
_fragmentOpcode = null
|
||||
return WebSocketMessage.new_(completeOpcode, completePayload, true)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (opcode == 1 || opcode == 2) {
|
||||
if (fin) {
|
||||
return frame
|
||||
} else {
|
||||
_fragmentOpcode = opcode
|
||||
_fragmentBuffer = []
|
||||
for (b in payload) {
|
||||
_fragmentBuffer.add(b)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Fiber.abort("Unknown opcode: %(opcode)")
|
||||
}
|
||||
}
|
||||
|
||||
sendFrame_(opcode, payload) {
|
||||
var frame = encodeFrame_(opcode, payload, _isClient)
|
||||
var data = ""
|
||||
for (b in frame) {
|
||||
data = data + String.fromByte(b)
|
||||
}
|
||||
_socket.write(data)
|
||||
}
|
||||
|
||||
encodeFrame_(opcode, payload, masked) {
|
||||
var frame = []
|
||||
|
||||
frame.add(0x80 | opcode)
|
||||
|
||||
var len = payload.count
|
||||
var maskBit = masked ? 0x80 : 0x00
|
||||
|
||||
if (len < 126) {
|
||||
frame.add(maskBit | len)
|
||||
} else if (len < 65536) {
|
||||
frame.add(maskBit | 126)
|
||||
frame.add((len >> 8) & 0xFF)
|
||||
frame.add(len & 0xFF)
|
||||
} else {
|
||||
frame.add(maskBit | 127)
|
||||
for (i in 0...4) frame.add(0)
|
||||
frame.add((len >> 24) & 0xFF)
|
||||
frame.add((len >> 16) & 0xFF)
|
||||
frame.add((len >> 8) & 0xFF)
|
||||
frame.add(len & 0xFF)
|
||||
}
|
||||
|
||||
if (masked) {
|
||||
var mask = Crypto.randomBytes(4)
|
||||
for (b in mask) frame.add(b)
|
||||
for (i in 0...len) {
|
||||
frame.add(payload[i] ^ mask[i % 4])
|
||||
}
|
||||
} else {
|
||||
for (b in payload) frame.add(b)
|
||||
}
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
readFrame_() {
|
||||
var header = readBytes_(2)
|
||||
if (header == null || header.count < 2) return null
|
||||
|
||||
var fin = (header[0] & 0x80) != 0
|
||||
var opcode = header[0] & 0x0F
|
||||
var masked = (header[1] & 0x80) != 0
|
||||
var len = header[1] & 0x7F
|
||||
|
||||
if (len == 126) {
|
||||
var ext = readBytes_(2)
|
||||
if (ext == null || ext.count < 2) return null
|
||||
len = (ext[0] << 8) | ext[1]
|
||||
} else if (len == 127) {
|
||||
var ext = readBytes_(8)
|
||||
if (ext == null || ext.count < 8) return null
|
||||
len = 0
|
||||
for (i in 4...8) {
|
||||
len = (len << 8) | ext[i]
|
||||
}
|
||||
}
|
||||
|
||||
var mask = null
|
||||
if (masked) {
|
||||
mask = readBytes_(4)
|
||||
if (mask == null || mask.count < 4) return null
|
||||
}
|
||||
|
||||
var payload = []
|
||||
if (len > 0) {
|
||||
payload = readBytes_(len)
|
||||
if (payload == null) return null
|
||||
}
|
||||
|
||||
if (masked && mask != null) {
|
||||
for (i in 0...payload.count) {
|
||||
payload[i] = payload[i] ^ mask[i % 4]
|
||||
}
|
||||
}
|
||||
|
||||
return WebSocketMessage.new_(opcode, payload, fin)
|
||||
}
|
||||
|
||||
readBytes_(count) {
|
||||
while (_readBuffer.count < count) {
|
||||
var chunk = _socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
if (_readBuffer.count == 0) return null
|
||||
var result = []
|
||||
for (b in _readBuffer) result.add(b)
|
||||
_readBuffer = []
|
||||
return result
|
||||
}
|
||||
for (b in chunk.bytes) {
|
||||
_readBuffer.add(b)
|
||||
}
|
||||
}
|
||||
var result = []
|
||||
for (i in 0...count) {
|
||||
result.add(_readBuffer[i])
|
||||
}
|
||||
var remaining = []
|
||||
for (i in count..._readBuffer.count) {
|
||||
remaining.add(_readBuffer[i])
|
||||
}
|
||||
_readBuffer = remaining
|
||||
return result
|
||||
}
|
||||
|
||||
stringToBytes_(str) {
|
||||
var bytes = []
|
||||
for (b in str.bytes) {
|
||||
bytes.add(b)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
class WebSocketServer {
|
||||
construct new_(server) {
|
||||
_server = server
|
||||
}
|
||||
|
||||
static bind(host, port) {
|
||||
var server = Server.bind(host, port)
|
||||
return WebSocketServer.new_(server)
|
||||
}
|
||||
|
||||
accept() {
|
||||
var socket = _server.accept()
|
||||
return upgradeConnection_(socket)
|
||||
}
|
||||
|
||||
upgradeConnection_(socket) {
|
||||
var request = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null || chunk.count == 0) {
|
||||
socket.close()
|
||||
return null
|
||||
}
|
||||
request = request + chunk
|
||||
if (request.indexOf("\r\n\r\n") >= 0) break
|
||||
}
|
||||
|
||||
var headerEnd = request.indexOf("\r\n\r\n")
|
||||
var headerPart = request[0...headerEnd]
|
||||
var lines = WebSocket.splitLines_(headerPart)
|
||||
|
||||
if (lines.count == 0) {
|
||||
socket.close()
|
||||
return null
|
||||
}
|
||||
|
||||
var key = null
|
||||
var isUpgrade = false
|
||||
var isWebSocket = false
|
||||
|
||||
for (i in 1...lines.count) {
|
||||
var line = lines[i]
|
||||
var colonPos = line.indexOf(":")
|
||||
if (colonPos > 0) {
|
||||
var name = WebSocket.toLower_(line[0...colonPos].trim())
|
||||
var value = line[(colonPos + 1)..-1].trim()
|
||||
|
||||
if (name == "sec-websocket-key") {
|
||||
key = value
|
||||
} else if (name == "upgrade" && WebSocket.toLower_(value) == "websocket") {
|
||||
isWebSocket = true
|
||||
} else if (name == "connection" && WebSocket.toLower_(value).contains("upgrade")) {
|
||||
isUpgrade = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isUpgrade || !isWebSocket || key == null) {
|
||||
var response = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
||||
socket.write(response)
|
||||
socket.close()
|
||||
return null
|
||||
}
|
||||
|
||||
var acceptKey = WebSocket.computeAcceptKey_(key)
|
||||
|
||||
var response = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
response = response + "Upgrade: websocket\r\n"
|
||||
response = response + "Connection: Upgrade\r\n"
|
||||
response = response + "Sec-WebSocket-Accept: %(acceptKey)\r\n"
|
||||
response = response + "\r\n"
|
||||
|
||||
socket.write(response)
|
||||
|
||||
return WebSocket.new_(socket, null, false)
|
||||
}
|
||||
|
||||
close() {
|
||||
_server.close()
|
||||
}
|
||||
}
|
||||
557
src/module/websocket.wren.inc
Normal file
557
src/module/websocket.wren.inc
Normal file
@ -0,0 +1,557 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/websocket.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* websocketModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"net\" for Socket, Server\n"
|
||||
"import \"tls\" for TlsSocket\n"
|
||||
"import \"dns\" for Dns\n"
|
||||
"import \"crypto\" for Crypto, Hash\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"\n"
|
||||
"class WebSocketMessage {\n"
|
||||
" construct new_(opcode, payload, fin) {\n"
|
||||
" _opcode = opcode\n"
|
||||
" _payload = payload\n"
|
||||
" _fin = fin\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" opcode { _opcode }\n"
|
||||
" payload { _payload }\n"
|
||||
" fin { _fin }\n"
|
||||
"\n"
|
||||
" isText { _opcode == 1 }\n"
|
||||
" isBinary { _opcode == 2 }\n"
|
||||
" isClose { _opcode == 8 }\n"
|
||||
" isPing { _opcode == 9 }\n"
|
||||
" isPong { _opcode == 10 }\n"
|
||||
"\n"
|
||||
" text {\n"
|
||||
" if (!isText) Fiber.abort(\"Message is not a text frame.\")\n"
|
||||
" return bytesToString_(_payload)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" bytes { _payload }\n"
|
||||
"\n"
|
||||
" closeCode {\n"
|
||||
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
|
||||
" if (_payload.count < 2) return 1005\n"
|
||||
" return (_payload[0] << 8) | _payload[1]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" closeReason {\n"
|
||||
" if (!isClose) Fiber.abort(\"Message is not a close frame.\")\n"
|
||||
" if (_payload.count <= 2) return \"\"\n"
|
||||
" return bytesToString_(_payload[2..-1])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static bytesToString_(bytes) {\n"
|
||||
" var str = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" str = str + String.fromCodePoint(b)\n"
|
||||
" }\n"
|
||||
" return str\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" bytesToString_(bytes) { WebSocketMessage.bytesToString_(bytes) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class WebSocket {\n"
|
||||
" construct new_(socket, url, isClient) {\n"
|
||||
" _socket = socket\n"
|
||||
" _url = url\n"
|
||||
" _isClient = isClient\n"
|
||||
" _isOpen = true\n"
|
||||
" _fragmentBuffer = []\n"
|
||||
" _fragmentOpcode = null\n"
|
||||
" _readBuffer = []\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static connect(url) { connect(url, {}) }\n"
|
||||
"\n"
|
||||
" static connect(url, headers) {\n"
|
||||
" var parsed = parseUrl_(url)\n"
|
||||
"\n"
|
||||
" if (parsed[\"scheme\"] != \"ws\" && parsed[\"scheme\"] != \"wss\") {\n"
|
||||
" Fiber.abort(\"Unsupported scheme: %(parsed[\"scheme\"]). Use ws:// or wss://\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var isSecure = parsed[\"scheme\"] == \"wss\"\n"
|
||||
" var host = parsed[\"host\"]\n"
|
||||
" var port = parsed[\"port\"]\n"
|
||||
" var path = parsed[\"path\"]\n"
|
||||
"\n"
|
||||
" var addresses = Dns.lookup(host, 4)\n"
|
||||
" if (addresses.count == 0) {\n"
|
||||
" Fiber.abort(\"Could not resolve host: %(host)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var socket\n"
|
||||
" if (isSecure) {\n"
|
||||
" socket = TlsSocket.connect(addresses[0], port, host)\n"
|
||||
" } else {\n"
|
||||
" socket = Socket.connect(addresses[0], port)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var keyBytes = Crypto.randomBytes(16)\n"
|
||||
" var key = Base64.encode(bytesToString_(keyBytes))\n"
|
||||
"\n"
|
||||
" var request = \"GET %(path) HTTP/1.1\\r\\n\"\n"
|
||||
" request = request + \"Host: %(host)\\r\\n\"\n"
|
||||
" request = request + \"Upgrade: websocket\\r\\n\"\n"
|
||||
" request = request + \"Connection: Upgrade\\r\\n\"\n"
|
||||
" request = request + \"Sec-WebSocket-Key: %(key)\\r\\n\"\n"
|
||||
" request = request + \"Sec-WebSocket-Version: 13\\r\\n\"\n"
|
||||
"\n"
|
||||
" for (entry in headers) {\n"
|
||||
" request = request + \"%(entry.key): %(entry.value)\\r\\n\"\n"
|
||||
" }\n"
|
||||
" request = request + \"\\r\\n\"\n"
|
||||
"\n"
|
||||
" socket.write(request)\n"
|
||||
"\n"
|
||||
" var response = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" Fiber.abort(\"Connection closed during handshake\")\n"
|
||||
" }\n"
|
||||
" response = response + chunk\n"
|
||||
" if (response.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var headerEnd = response.indexOf(\"\\r\\n\\r\\n\")\n"
|
||||
" var headerPart = response[0...headerEnd]\n"
|
||||
" var lines = splitLines_(headerPart)\n"
|
||||
"\n"
|
||||
" if (lines.count == 0) {\n"
|
||||
" Fiber.abort(\"Invalid WebSocket handshake response\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var statusLine = lines[0]\n"
|
||||
" if (!statusLine.contains(\"101\")) {\n"
|
||||
" Fiber.abort(\"WebSocket handshake failed: %(statusLine)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var expectedAccept = computeAcceptKey_(key)\n"
|
||||
" var receivedAccept = null\n"
|
||||
"\n"
|
||||
" for (i in 1...lines.count) {\n"
|
||||
" var line = lines[i]\n"
|
||||
" var colonPos = line.indexOf(\":\")\n"
|
||||
" if (colonPos > 0) {\n"
|
||||
" var name = toLower_(line[0...colonPos].trim())\n"
|
||||
" var value = line[(colonPos + 1)..-1].trim()\n"
|
||||
" if (name == \"sec-websocket-accept\") {\n"
|
||||
" receivedAccept = value\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (receivedAccept != expectedAccept) {\n"
|
||||
" Fiber.abort(\"Invalid Sec-WebSocket-Accept header\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return WebSocket.new_(socket, url, true)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseUrl_(url) {\n"
|
||||
" var result = {\n"
|
||||
" \"scheme\": \"ws\",\n"
|
||||
" \"host\": \"\",\n"
|
||||
" \"port\": 80,\n"
|
||||
" \"path\": \"/\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var rest = url\n"
|
||||
"\n"
|
||||
" var schemeEnd = rest.indexOf(\"://\")\n"
|
||||
" if (schemeEnd >= 0) {\n"
|
||||
" result[\"scheme\"] = toLower_(rest[0...schemeEnd])\n"
|
||||
" rest = rest[(schemeEnd + 3)..-1]\n"
|
||||
" if (result[\"scheme\"] == \"wss\") result[\"port\"] = 443\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var pathStart = rest.indexOf(\"/\")\n"
|
||||
" var hostPart = pathStart >= 0 ? rest[0...pathStart] : rest\n"
|
||||
" result[\"path\"] = pathStart >= 0 ? rest[pathStart..-1] : \"/\"\n"
|
||||
"\n"
|
||||
" var portStart = hostPart.indexOf(\":\")\n"
|
||||
" if (portStart >= 0) {\n"
|
||||
" result[\"host\"] = hostPart[0...portStart]\n"
|
||||
" result[\"port\"] = Num.fromString(hostPart[(portStart + 1)..-1])\n"
|
||||
" } else {\n"
|
||||
" result[\"host\"] = hostPart\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static splitLines_(text) {\n"
|
||||
" var lines = []\n"
|
||||
" var start = 0\n"
|
||||
" var i = 0\n"
|
||||
" while (i < text.count) {\n"
|
||||
" if (text[i] == \"\\r\" && i + 1 < text.count && text[i + 1] == \"\\n\") {\n"
|
||||
" lines.add(text[start...i])\n"
|
||||
" start = i + 2\n"
|
||||
" i = i + 2\n"
|
||||
" } else {\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (start < text.count) {\n"
|
||||
" lines.add(text[start..-1])\n"
|
||||
" }\n"
|
||||
" return lines\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + String.fromCodePoint(cp + 32)\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static computeAcceptKey_(key) {\n"
|
||||
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
|
||||
" var combined = key + magic\n"
|
||||
" var sha1 = Hash.sha1(combined)\n"
|
||||
" var sha1Str = bytesToString_(sha1)\n"
|
||||
" return Base64.encode(sha1Str)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static bytesToString_(bytes) {\n"
|
||||
" var str = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" str = str + String.fromByte(b)\n"
|
||||
" }\n"
|
||||
" return str\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" url { _url }\n"
|
||||
" isOpen { _isOpen }\n"
|
||||
"\n"
|
||||
" send(text) {\n"
|
||||
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
|
||||
" if (!(text is String)) Fiber.abort(\"Data must be a string.\")\n"
|
||||
" var payload = stringToBytes_(text)\n"
|
||||
" sendFrame_(1, payload)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" sendBinary(bytes) {\n"
|
||||
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
|
||||
" if (!(bytes is List)) Fiber.abort(\"Data must be a list of bytes.\")\n"
|
||||
" sendFrame_(2, bytes)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ping() { ping([]) }\n"
|
||||
"\n"
|
||||
" ping(data) {\n"
|
||||
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
|
||||
" var payload = data\n"
|
||||
" if (data is String) payload = stringToBytes_(data)\n"
|
||||
" if (payload.count > 125) Fiber.abort(\"Ping payload too large (max 125 bytes).\")\n"
|
||||
" sendFrame_(9, payload)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" pong(data) {\n"
|
||||
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
|
||||
" var payload = data\n"
|
||||
" if (data is String) payload = stringToBytes_(data)\n"
|
||||
" if (payload.count > 125) Fiber.abort(\"Pong payload too large (max 125 bytes).\")\n"
|
||||
" sendFrame_(10, payload)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" close() { close(1000, \"\") }\n"
|
||||
"\n"
|
||||
" close(code, reason) {\n"
|
||||
" if (!_isOpen) return\n"
|
||||
"\n"
|
||||
" var payload = []\n"
|
||||
" payload.add((code >> 8) & 0xFF)\n"
|
||||
" payload.add(code & 0xFF)\n"
|
||||
" for (b in stringToBytes_(reason)) {\n"
|
||||
" payload.add(b)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" sendFrame_(8, payload)\n"
|
||||
" _isOpen = false\n"
|
||||
" _socket.close()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" receive() {\n"
|
||||
" if (!_isOpen) return null\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var frame = readFrame_()\n"
|
||||
" if (frame == null) {\n"
|
||||
" _isOpen = false\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var opcode = frame.opcode\n"
|
||||
" var payload = frame.payload\n"
|
||||
" var fin = frame.fin\n"
|
||||
"\n"
|
||||
" if (opcode == 8) {\n"
|
||||
" _isOpen = false\n"
|
||||
" if (_isClient) {\n"
|
||||
" sendFrame_(8, payload)\n"
|
||||
" _socket.close()\n"
|
||||
" }\n"
|
||||
" return frame\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (opcode == 9) {\n"
|
||||
" sendFrame_(10, payload)\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (opcode == 10) {\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (opcode == 0) {\n"
|
||||
" for (b in payload) {\n"
|
||||
" _fragmentBuffer.add(b)\n"
|
||||
" }\n"
|
||||
" if (fin) {\n"
|
||||
" var completePayload = _fragmentBuffer\n"
|
||||
" var completeOpcode = _fragmentOpcode\n"
|
||||
" _fragmentBuffer = []\n"
|
||||
" _fragmentOpcode = null\n"
|
||||
" return WebSocketMessage.new_(completeOpcode, completePayload, true)\n"
|
||||
" }\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (opcode == 1 || opcode == 2) {\n"
|
||||
" if (fin) {\n"
|
||||
" return frame\n"
|
||||
" } else {\n"
|
||||
" _fragmentOpcode = opcode\n"
|
||||
" _fragmentBuffer = []\n"
|
||||
" for (b in payload) {\n"
|
||||
" _fragmentBuffer.add(b)\n"
|
||||
" }\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" Fiber.abort(\"Unknown opcode: %(opcode)\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" sendFrame_(opcode, payload) {\n"
|
||||
" var frame = encodeFrame_(opcode, payload, _isClient)\n"
|
||||
" var data = \"\"\n"
|
||||
" for (b in frame) {\n"
|
||||
" data = data + String.fromByte(b)\n"
|
||||
" }\n"
|
||||
" _socket.write(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" encodeFrame_(opcode, payload, masked) {\n"
|
||||
" var frame = []\n"
|
||||
"\n"
|
||||
" frame.add(0x80 | opcode)\n"
|
||||
"\n"
|
||||
" var len = payload.count\n"
|
||||
" var maskBit = masked ? 0x80 : 0x00\n"
|
||||
"\n"
|
||||
" if (len < 126) {\n"
|
||||
" frame.add(maskBit | len)\n"
|
||||
" } else if (len < 65536) {\n"
|
||||
" frame.add(maskBit | 126)\n"
|
||||
" frame.add((len >> 8) & 0xFF)\n"
|
||||
" frame.add(len & 0xFF)\n"
|
||||
" } else {\n"
|
||||
" frame.add(maskBit | 127)\n"
|
||||
" for (i in 0...4) frame.add(0)\n"
|
||||
" frame.add((len >> 24) & 0xFF)\n"
|
||||
" frame.add((len >> 16) & 0xFF)\n"
|
||||
" frame.add((len >> 8) & 0xFF)\n"
|
||||
" frame.add(len & 0xFF)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (masked) {\n"
|
||||
" var mask = Crypto.randomBytes(4)\n"
|
||||
" for (b in mask) frame.add(b)\n"
|
||||
" for (i in 0...len) {\n"
|
||||
" frame.add(payload[i] ^ mask[i % 4])\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" for (b in payload) frame.add(b)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return frame\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" readFrame_() {\n"
|
||||
" var header = readBytes_(2)\n"
|
||||
" if (header == null || header.count < 2) return null\n"
|
||||
"\n"
|
||||
" var fin = (header[0] & 0x80) != 0\n"
|
||||
" var opcode = header[0] & 0x0F\n"
|
||||
" var masked = (header[1] & 0x80) != 0\n"
|
||||
" var len = header[1] & 0x7F\n"
|
||||
"\n"
|
||||
" if (len == 126) {\n"
|
||||
" var ext = readBytes_(2)\n"
|
||||
" if (ext == null || ext.count < 2) return null\n"
|
||||
" len = (ext[0] << 8) | ext[1]\n"
|
||||
" } else if (len == 127) {\n"
|
||||
" var ext = readBytes_(8)\n"
|
||||
" if (ext == null || ext.count < 8) return null\n"
|
||||
" len = 0\n"
|
||||
" for (i in 4...8) {\n"
|
||||
" len = (len << 8) | ext[i]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var mask = null\n"
|
||||
" if (masked) {\n"
|
||||
" mask = readBytes_(4)\n"
|
||||
" if (mask == null || mask.count < 4) return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var payload = []\n"
|
||||
" if (len > 0) {\n"
|
||||
" payload = readBytes_(len)\n"
|
||||
" if (payload == null) return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (masked && mask != null) {\n"
|
||||
" for (i in 0...payload.count) {\n"
|
||||
" payload[i] = payload[i] ^ mask[i % 4]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return WebSocketMessage.new_(opcode, payload, fin)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" readBytes_(count) {\n"
|
||||
" while (_readBuffer.count < count) {\n"
|
||||
" var chunk = _socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" if (_readBuffer.count == 0) return null\n"
|
||||
" var result = []\n"
|
||||
" for (b in _readBuffer) result.add(b)\n"
|
||||
" _readBuffer = []\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" for (b in chunk.bytes) {\n"
|
||||
" _readBuffer.add(b)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" var result = []\n"
|
||||
" for (i in 0...count) {\n"
|
||||
" result.add(_readBuffer[i])\n"
|
||||
" }\n"
|
||||
" var remaining = []\n"
|
||||
" for (i in count..._readBuffer.count) {\n"
|
||||
" remaining.add(_readBuffer[i])\n"
|
||||
" }\n"
|
||||
" _readBuffer = remaining\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" stringToBytes_(str) {\n"
|
||||
" var bytes = []\n"
|
||||
" for (b in str.bytes) {\n"
|
||||
" bytes.add(b)\n"
|
||||
" }\n"
|
||||
" return bytes\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class WebSocketServer {\n"
|
||||
" construct new_(server) {\n"
|
||||
" _server = server\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static bind(host, port) {\n"
|
||||
" var server = Server.bind(host, port)\n"
|
||||
" return WebSocketServer.new_(server)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" accept() {\n"
|
||||
" var socket = _server.accept()\n"
|
||||
" return upgradeConnection_(socket)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" upgradeConnection_(socket) {\n"
|
||||
" var request = \"\"\n"
|
||||
" while (true) {\n"
|
||||
" var chunk = socket.read()\n"
|
||||
" if (chunk == null || chunk.count == 0) {\n"
|
||||
" socket.close()\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
" request = request + chunk\n"
|
||||
" if (request.indexOf(\"\\r\\n\\r\\n\") >= 0) break\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var headerEnd = request.indexOf(\"\\r\\n\\r\\n\")\n"
|
||||
" var headerPart = request[0...headerEnd]\n"
|
||||
" var lines = WebSocket.splitLines_(headerPart)\n"
|
||||
"\n"
|
||||
" if (lines.count == 0) {\n"
|
||||
" socket.close()\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var key = null\n"
|
||||
" var isUpgrade = false\n"
|
||||
" var isWebSocket = false\n"
|
||||
"\n"
|
||||
" for (i in 1...lines.count) {\n"
|
||||
" var line = lines[i]\n"
|
||||
" var colonPos = line.indexOf(\":\")\n"
|
||||
" if (colonPos > 0) {\n"
|
||||
" var name = WebSocket.toLower_(line[0...colonPos].trim())\n"
|
||||
" var value = line[(colonPos + 1)..-1].trim()\n"
|
||||
"\n"
|
||||
" if (name == \"sec-websocket-key\") {\n"
|
||||
" key = value\n"
|
||||
" } else if (name == \"upgrade\" && WebSocket.toLower_(value) == \"websocket\") {\n"
|
||||
" isWebSocket = true\n"
|
||||
" } else if (name == \"connection\" && WebSocket.toLower_(value).contains(\"upgrade\")) {\n"
|
||||
" isUpgrade = true\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (!isUpgrade || !isWebSocket || key == null) {\n"
|
||||
" var response = \"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\"\n"
|
||||
" socket.write(response)\n"
|
||||
" socket.close()\n"
|
||||
" return null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var acceptKey = WebSocket.computeAcceptKey_(key)\n"
|
||||
"\n"
|
||||
" var response = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n"
|
||||
" response = response + \"Upgrade: websocket\\r\\n\"\n"
|
||||
" response = response + \"Connection: Upgrade\\r\\n\"\n"
|
||||
" response = response + \"Sec-WebSocket-Accept: %(acceptKey)\\r\\n\"\n"
|
||||
" response = response + \"\\r\\n\"\n"
|
||||
"\n"
|
||||
" socket.write(response)\n"
|
||||
"\n"
|
||||
" return WebSocket.new_(socket, null, false)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" close() {\n"
|
||||
" _server.close()\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
18
test/base64/base64.wren
vendored
Normal file
18
test/base64/base64.wren
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
import "base64" for Base64
|
||||
|
||||
System.print(Base64.encode("Hello")) // expect: SGVsbG8=
|
||||
System.print(Base64.encode("Hello, World!")) // expect: SGVsbG8sIFdvcmxkIQ==
|
||||
System.print(Base64.encode("")) // expect:
|
||||
System.print(Base64.encode("a")) // expect: YQ==
|
||||
System.print(Base64.encode("ab")) // expect: YWI=
|
||||
System.print(Base64.encode("abc")) // expect: YWJj
|
||||
|
||||
System.print(Base64.decode("SGVsbG8=")) // expect: Hello
|
||||
System.print(Base64.decode("SGVsbG8sIFdvcmxkIQ==")) // expect: Hello, World!
|
||||
System.print(Base64.decode("")) // expect:
|
||||
System.print(Base64.decode("YQ==")) // expect: a
|
||||
System.print(Base64.decode("YWI=")) // expect: ab
|
||||
System.print(Base64.decode("YWJj")) // expect: abc
|
||||
|
||||
System.print(Base64.encodeUrl("Hello+World/Test")) // expect: SGVsbG8rV29ybGQvVGVzdA
|
||||
System.print(Base64.decode(Base64.encode("Test roundtrip"))) // expect: Test roundtrip
|
||||
19
test/crypto/crypto.wren
vendored
Normal file
19
test/crypto/crypto.wren
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "crypto" for Crypto, Hash
|
||||
|
||||
var bytes = Crypto.randomBytes(16)
|
||||
System.print(bytes is List) // expect: true
|
||||
System.print(bytes.count) // expect: 16
|
||||
|
||||
var md5 = Hash.md5("hello")
|
||||
System.print(Hash.toHex(md5)) // expect: 5d41402abc4b2a76b9719d911017c592
|
||||
|
||||
var sha1 = Hash.sha1("hello")
|
||||
System.print(Hash.toHex(sha1)) // expect: aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
|
||||
|
||||
var sha256 = Hash.sha256("hello")
|
||||
System.print(Hash.toHex(sha256)) // expect: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
|
||||
|
||||
var emptyMd5 = Hash.md5("")
|
||||
System.print(Hash.toHex(emptyMd5)) // expect: d41d8cd98f00b204e9800998ecf8427e
|
||||
28
test/datetime/datetime.wren
vendored
Normal file
28
test/datetime/datetime.wren
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import "datetime" for DateTime, Duration
|
||||
|
||||
var now = DateTime.now()
|
||||
System.print(now.year >= 2024) // expect: true
|
||||
System.print(now.month >= 1 && now.month <= 12) // expect: true
|
||||
System.print(now.day >= 1 && now.day <= 31) // expect: true
|
||||
System.print(now.hour >= 0 && now.hour <= 23) // expect: true
|
||||
System.print(now.minute >= 0 && now.minute <= 59) // expect: true
|
||||
System.print(now.second >= 0 && now.second <= 59) // expect: true
|
||||
|
||||
var ts = DateTime.fromTimestamp(0)
|
||||
System.print(ts.year) // expect: 1970
|
||||
|
||||
var d1 = Duration.fromSeconds(60)
|
||||
System.print(d1.seconds) // expect: 60
|
||||
System.print(d1.minutes) // expect: 1
|
||||
|
||||
var d2 = Duration.fromMinutes(2)
|
||||
System.print(d2.seconds) // expect: 120
|
||||
|
||||
var d3 = Duration.fromHours(1)
|
||||
System.print(d3.minutes) // expect: 60
|
||||
|
||||
var d4 = Duration.fromDays(1)
|
||||
System.print(d4.hours) // expect: 24
|
||||
|
||||
var sum = d1 + d2
|
||||
System.print(sum.seconds) // expect: 180
|
||||
8
test/dns/dns.wren
vendored
Normal file
8
test/dns/dns.wren
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import "dns" for Dns
|
||||
|
||||
var addresses = Dns.lookup("localhost")
|
||||
System.print(addresses is List) // expect: true
|
||||
System.print(addresses.count > 0) // expect: true
|
||||
|
||||
var first = addresses[0]
|
||||
System.print(first == "127.0.0.1" || first == "::1") // expect: true
|
||||
19
test/env/environment.wren
vendored
Normal file
19
test/env/environment.wren
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import "env" for Environment
|
||||
|
||||
Environment.set("WREN_TEST_VAR", "hello")
|
||||
System.print(Environment.get("WREN_TEST_VAR")) // expect: hello
|
||||
|
||||
Environment.set("WREN_TEST_VAR", "world")
|
||||
System.print(Environment.get("WREN_TEST_VAR")) // expect: world
|
||||
|
||||
Environment.delete("WREN_TEST_VAR")
|
||||
System.print(Environment.get("WREN_TEST_VAR") == null) // expect: true
|
||||
|
||||
System.print(Environment.get("THIS_VAR_DOES_NOT_EXIST") == null) // expect: true
|
||||
|
||||
var home = Environment.get("HOME")
|
||||
System.print(home != null) // expect: true
|
||||
|
||||
var all = Environment.all
|
||||
System.print(all is Map) // expect: true
|
||||
System.print(all.count > 0) // expect: true
|
||||
19
test/http/http.wren
vendored
Normal file
19
test/http/http.wren
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "http" for Url, Http, HttpResponse
|
||||
|
||||
var url1 = Url.parse("http://example.com/path")
|
||||
System.print(url1.scheme) // expect: http
|
||||
System.print(url1.host) // expect: example.com
|
||||
System.print(url1.port) // expect: 80
|
||||
System.print(url1.path) // expect: /path
|
||||
|
||||
var url2 = Url.parse("http://example.com:8080/test?foo=bar")
|
||||
System.print(url2.port) // expect: 8080
|
||||
System.print(url2.path) // expect: /test
|
||||
System.print(url2.query) // expect: foo=bar
|
||||
System.print(url2.fullPath) // expect: /test?foo=bar
|
||||
|
||||
var url3 = Url.parse("http://localhost/")
|
||||
System.print(url3.host) // expect: localhost
|
||||
System.print(url3.path) // expect: /
|
||||
13
test/jinja/comments.wren
vendored
Normal file
13
test/jinja/comments.wren
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"simple": "before{# comment #}after",
|
||||
"multiword": "a{# this is a comment #}b",
|
||||
"with_tags": "x{# {{ ignored }} #}y"
|
||||
}))
|
||||
|
||||
System.print(env.getTemplate("simple").render({})) // expect: beforeafter
|
||||
System.print(env.getTemplate("multiword").render({})) // expect: ab
|
||||
System.print(env.getTemplate("with_tags").render({})) // expect: xy
|
||||
32
test/jinja/conditionals.wren
vendored
Normal file
32
test/jinja/conditionals.wren
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"if_true": "{\% if show \%}visible{\% endif \%}",
|
||||
"if_false": "{\% if hide \%}visible{\% endif \%}",
|
||||
"if_else": "{\% if show \%}yes{\% else \%}no{\% endif \%}",
|
||||
"if_elif": "{\% if x == 1 \%}one{\% elif x == 2 \%}two{\% else \%}other{\% endif \%}",
|
||||
"compare": "{\% if a > b \%}greater{\% elif a < b \%}less{\% else \%}equal{\% endif \%}",
|
||||
"and_or": "{\% if a and b \%}both{\% elif a or b \%}one{\% else \%}none{\% endif \%}",
|
||||
"not": "{\% if not hidden \%}shown{\% endif \%}",
|
||||
"in_list": "{\% if item in items \%}found{\% else \%}missing{\% endif \%}",
|
||||
"is_even": "{\% if num is even \%}even{\% else \%}odd{\% endif \%}",
|
||||
"is_defined": "{\% if var is defined \%}defined{\% else \%}undefined{\% endif \%}",
|
||||
"is_not": "{\% if num is not odd \%}not odd{\% endif \%}",
|
||||
"ternary": "{{ \"yes\" if show else \"no\" }}"
|
||||
}))
|
||||
|
||||
System.print(env.getTemplate("if_true").render({"show": true})) // expect: visible
|
||||
System.print(env.getTemplate("if_false").render({"hide": false})) // expect:
|
||||
System.print(env.getTemplate("if_else").render({"show": false})) // expect: no
|
||||
System.print(env.getTemplate("if_elif").render({"x": 2})) // expect: two
|
||||
System.print(env.getTemplate("compare").render({"a": 5, "b": 3})) // expect: greater
|
||||
System.print(env.getTemplate("and_or").render({"a": true, "b": false})) // expect: one
|
||||
System.print(env.getTemplate("not").render({"hidden": false})) // expect: shown
|
||||
System.print(env.getTemplate("in_list").render({"item": "b", "items": ["a", "b", "c"]})) // expect: found
|
||||
System.print(env.getTemplate("is_even").render({"num": 4})) // expect: even
|
||||
System.print(env.getTemplate("is_defined").render({"var": "something"})) // expect: defined
|
||||
System.print(env.getTemplate("is_defined").render({})) // expect: undefined
|
||||
System.print(env.getTemplate("is_not").render({"num": 4})) // expect: not odd
|
||||
System.print(env.getTemplate("ternary").render({"show": true})) // expect: yes
|
||||
35
test/jinja/expressions.wren
vendored
Normal file
35
test/jinja/expressions.wren
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"math": "{{ 2 + 3 * 4 }}",
|
||||
"paren": "{{ (2 + 3) * 4 }}",
|
||||
"mod": "{{ 10 \% 3 }}",
|
||||
"div": "{{ 10 / 4 }}",
|
||||
"neg": "{{ -5 }}",
|
||||
"compare_chain": "{\% if 1 < 2 and 2 < 3 \%}yes{\% endif \%}",
|
||||
"bool_and": "{{ true and false }}",
|
||||
"bool_or": "{{ true or false }}",
|
||||
"bool_not": "{{ not false }}",
|
||||
"string_in": "{\% if \"b\" in \"abc\" \%}found{\% endif \%}",
|
||||
"list_in": "{\% if 2 in [1, 2, 3] \%}found{\% endif \%}",
|
||||
"nested_access": "{{ data.items[0].name }}",
|
||||
"filter_chain": "{{ text|trim|upper }}",
|
||||
"method_call": "{{ items|join(\", \")|upper }}"
|
||||
}))
|
||||
|
||||
System.print(env.getTemplate("math").render({})) // expect: 14
|
||||
System.print(env.getTemplate("paren").render({})) // expect: 20
|
||||
System.print(env.getTemplate("mod").render({})) // expect: 1
|
||||
System.print(env.getTemplate("div").render({})) // expect: 2.5
|
||||
System.print(env.getTemplate("neg").render({})) // expect: -5
|
||||
System.print(env.getTemplate("compare_chain").render({})) // expect: yes
|
||||
System.print(env.getTemplate("bool_and").render({})) // expect: false
|
||||
System.print(env.getTemplate("bool_or").render({})) // expect: true
|
||||
System.print(env.getTemplate("bool_not").render({})) // expect: true
|
||||
System.print(env.getTemplate("string_in").render({})) // expect: found
|
||||
System.print(env.getTemplate("list_in").render({})) // expect: found
|
||||
System.print(env.getTemplate("nested_access").render({"data": {"items": [{"name": "test"}]}})) // expect: test
|
||||
System.print(env.getTemplate("filter_chain").render({"text": " hello "})) // expect: HELLO
|
||||
System.print(env.getTemplate("method_call").render({"items": ["a", "b", "c"]})) // expect: A, B, C
|
||||
63
test/jinja/filters.wren
vendored
Normal file
63
test/jinja/filters.wren
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"upper": "{{ text|upper }}",
|
||||
"lower": "{{ text|lower }}",
|
||||
"capitalize": "{{ text|capitalize }}",
|
||||
"title": "{{ text|title }}",
|
||||
"trim": "[{{ text|trim }}]",
|
||||
"length": "{{ items|length }}",
|
||||
"first": "{{ items|first }}",
|
||||
"last": "{{ items|last }}",
|
||||
"reverse": "{{ items|reverse|join }}",
|
||||
"sort": "{{ items|sort|join(\", \") }}",
|
||||
"join": "{{ items|join(\"-\") }}",
|
||||
"default": "{{ missing|default(\"N/A\") }}",
|
||||
"int": "{{ num|int }}",
|
||||
"abs": "{{ num|abs }}",
|
||||
"round": "{{ num|round(2) }}",
|
||||
"replace": "{{ text|replace(\"o\", \"0\") }}",
|
||||
"truncate": "{{ text|truncate(10) }}",
|
||||
"batch": "{\% for row in items|batch(2) \%}[{\% for i in row \%}{{ i }}{\% endfor \%}]{\% endfor \%}",
|
||||
"unique": "{{ items|unique|join }}",
|
||||
"sum": "{{ nums|sum }}",
|
||||
"min": "{{ nums|min }}",
|
||||
"max": "{{ nums|max }}",
|
||||
"striptags": "{{ html|striptags }}",
|
||||
"urlencode": "{{ text|urlencode }}",
|
||||
"wordcount": "{{ text|wordcount }}",
|
||||
"center": "[{{ text|center(10) }}]",
|
||||
"indent": "{{ text|indent(4) }}",
|
||||
"tojson": "{{ data|tojson }}"
|
||||
}))
|
||||
|
||||
System.print(env.getTemplate("upper").render({"text": "hello"})) // expect: HELLO
|
||||
System.print(env.getTemplate("lower").render({"text": "HELLO"})) // expect: hello
|
||||
System.print(env.getTemplate("capitalize").render({"text": "hello world"})) // expect: Hello world
|
||||
System.print(env.getTemplate("title").render({"text": "hello world"})) // expect: Hello World
|
||||
System.print(env.getTemplate("trim").render({"text": " hi "})) // expect: [hi]
|
||||
System.print(env.getTemplate("length").render({"items": [1, 2, 3]})) // expect: 3
|
||||
System.print(env.getTemplate("first").render({"items": ["a", "b", "c"]})) // expect: a
|
||||
System.print(env.getTemplate("last").render({"items": ["a", "b", "c"]})) // expect: c
|
||||
System.print(env.getTemplate("reverse").render({"items": ["a", "b", "c"]})) // expect: cba
|
||||
System.print(env.getTemplate("sort").render({"items": ["c", "a", "b"]})) // expect: a, b, c
|
||||
System.print(env.getTemplate("join").render({"items": ["a", "b", "c"]})) // expect: a-b-c
|
||||
System.print(env.getTemplate("default").render({})) // expect: N/A
|
||||
System.print(env.getTemplate("int").render({"num": 3.7})) // expect: 3
|
||||
System.print(env.getTemplate("abs").render({"num": -5})) // expect: 5
|
||||
System.print(env.getTemplate("round").render({"num": 3.14159})) // expect: 3.14
|
||||
System.print(env.getTemplate("replace").render({"text": "hello"})) // expect: hell0
|
||||
System.print(env.getTemplate("truncate").render({"text": "hello world this is long"})) // expect: hello...
|
||||
System.print(env.getTemplate("batch").render({"items": [1, 2, 3, 4]})) // expect: [12][34]
|
||||
System.print(env.getTemplate("unique").render({"items": ["a", "b", "a", "c"]})) // expect: abc
|
||||
System.print(env.getTemplate("sum").render({"nums": [1, 2, 3, 4]})) // expect: 10
|
||||
System.print(env.getTemplate("min").render({"nums": [3, 1, 4, 1, 5]})) // expect: 1
|
||||
System.print(env.getTemplate("max").render({"nums": [3, 1, 4, 1, 5]})) // expect: 5
|
||||
System.print(env.getTemplate("striptags").render({"html": "<p>text</p>"})) // expect: text
|
||||
System.print(env.getTemplate("urlencode").render({"text": "hello world"})) // expect: hello+world
|
||||
System.print(env.getTemplate("wordcount").render({"text": "one two three"})) // expect: 3
|
||||
System.print(env.getTemplate("center").render({"text": "hi"})) // expect: [ hi ]
|
||||
System.print(env.getTemplate("indent").render({"text": "line"})) // expect: line
|
||||
System.print(env.getTemplate("tojson").render({"data": {"a": 1}})) // expect: {"a": 1}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user