This commit is contained in:
Edgar 2021-12-23 17:38:19 +01:00
parent d35452cff6
commit e337886e5b
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
19 changed files with 1326 additions and 0 deletions

25
config.toml Normal file
View file

@ -0,0 +1,25 @@
# The URL the site will be built for
base_url = "https://edgarluque.com"
title = "Edgar Luque"
description = "My personal website."
default_language = "en"
# Whether to automatically compile all Sass files in the sass directory
compile_sass = true
minify_html = true
# Whether to build a search index to be used later on by a JavaScript library
build_search_index = true
[markdown]
# Whether to do syntax highlighting
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
highlight_code = true
highlight_theme = "inspired-github"
[extra]
# Put all your custom variables here

10
content/blog/_index.md Normal file
View file

@ -0,0 +1,10 @@
+++
title = "Blog"
description = "My blog about software, programming and more."
sort_by = "date"
template = "index.html"
page_template = "blog-page.html"
generate_feed = true
in_search_index = true
aliases = ["blog"]
+++

View file

@ -0,0 +1,172 @@
+++
title = "Implementing a chat command in DDraceNetwork"
description = "A chat command that shows info about our player"
date = 2020-12-04
+++
This is the part 3 of my series of articles about coding in DDraceNetwork, you can find the first article [here](/blog/intro-to-ddnet).
We will implement a command that shows info about our player: `/aboutme <times>`
Times will be how many times we print this info.
Go to `src/game/server/ddracechat.h`
Here you can see all the chat commands, they are created using a macro.
## The chat command macro
The macro looks like this:
```cpp
#define CHAT_COMMAND(name, params, flags, callback, userdata, help)
```
The first field is the name of the command, we will create a command called "aboutme".
The second field is the command parameters, it uses a special syntax:
Add a `?` if it's optional.
A `i` for integers, `s` for a string, `r` for "everything else".
You can give a hint by using `[]`, like `r[player name]` or possible values `?i['0'|'1'|'2']`.
The next field are the flags, for server-side chat commands they are always:
```cpp
CFGFLAG_CHAT | CFGFLAG_SERVER
```
Then comes the name of our method, usually prefixed by Con: `ConRules`.
Next field is userdata, always pass `this`.
Then comes the help text, put whathever you see fit.
We will use this command definition:
```cpp
CHAT_COMMAND("aboutme", "?i[times]", CFGFLAG_CHAT | CFGFLAG_SERVER,
ConAboutMe, this, "Show info about yourself");
```
## Adding the static method
We added `ConAboutMe` on the CHAT_COMMAND macro, now we need to implement it.
First go to `src/game/server/gamecontext.h` and under the last static Con command you see add it:
```cpp
// ...
static void ConFreezeHammer(IConsole::IResult *pResult, void *pUserData);
static void ConUnFreezeHammer(IConsole::IResult *pResult, void *pUserData);
// Here!
static void ConAboutMe(IConsole::IResult *pResult, void *pUserData);
```
## Implementing the chat command
Then to implement it we go to `src/game/server/ddracechat.cpp` and add it to the end:
```cpp
void CGameContext::ConAboutMe(IConsole::IResult *pResult, void *pUserData)
{
/// The following code will be added here.
}
```
As you have seen, ConAboutMe is a static method, so in order to get hold of a CGameContext instance which lets us access all the information, we need to get it from `pUserData`.
```cpp
CGameContext *pSelf = (CGameContext *)pUserData;
```
Here `pResult` holds information about the caller client id, the number of arguments and how to get them.
Just to be on the safe side, we check that the ClientID we got is actually valid:
```cpp
if(!CheckClientID(pResult->m_ClientID))
return;
```
> When you are new to the ddnet codebase, a really useful thing to do is to check how similar things you are doing are implemented, in our case there are a lot of other chat commands and just by looking at their implementations we can learn lot of things, like checking the client id, how to print to the console, chat, etc...
Now we will handle our optional argument "times", which tells us how many times we will print this information.
```cpp
int Times = 1;
if(pResult->NumArguments() > 0)
Times = pResult->GetInteger(0);
if(Times < 1)
Times = 1;
```
As you can see `pResult` has some handy methods, aside from those 2 seen in the snippet, you can also get a float, string and a color. You can find out more [here](https://github.com/ddnet/ddnet/blob/516c1cc59986fee338710c215a7dc0c9f318faec/src/engine/console.h#L37).
In our command we want to get the following information: name, client version, team and position.
The player is always present while the character is only present if the player has a physical body (the tee).
```cpp
const char *pName = pSelf->Server()->ClientName(pResult->m_ClientID);
int ClientVersion = pSelf->GetClientVersion(pResult->m_ClientID);
int Team = pSelf->GetDDRaceTeam(pResult->m_ClientID);
CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID];
// Check if it's null
if(!pPlayer)
{
dbg_msg("chat-about-me", "Player not found!");
return;
}
CCharacter *pChar = pPlayer->GetCharacter();
// Check if it's null
if(!pChar)
{
dbg_msg("chat-about-me", "Character not found! Player may be dead or spectating.");
return;
}
```
You can see we use `dbg_msg` to print debug messages, an alternative is to print to console, but I recommend using `dbg_msg`.
Some methods to get information may not be on the class CGameContext, for example with the player name, we had to access the IServer class with the method `Server()`.
That said, now we send the chat message:
```cpp
char aBuf[256];
str_format(aBuf, sizeof(aBuf),
"Name: %s | "
"ID: %d | "
"Version: %d | "
"Team: %d | "
"Current position: %f, %f",
pName,
pResult->m_ClientID,
ClientVersion,
Team,
pChar->m_Pos.x, pChar->m_Pos.y);
for(int i = 0; i < Times; i++)
{
pSelf->SendChatTarget(pResult->m_ClientID, aBuf);
}
```
We format our message with `str_format` and send it with the method `SendChatTarget` and the client id we get from `pResult`.
And we can test it in the game:
![The result](/static/img/ddnet_chat_cmd.png)
## More to come
- Implementing a rcon command
- Adding configuration options to the client.
- Modify the menus to show a label and a button/checkbox/slider for the previously added options.
- Add a new network packet.
- Add a new map tile.
- Any other idea I may get in the future.

View file

@ -0,0 +1,64 @@
+++
title = "Creating precompiled headers with cmake"
description = "A brief introduction to target_precompile_headers"
date = 2020-11-12
+++
## What are precompiled headers?
They are a partially processed version of header files, this speeds up compilation because it doesn't have to repeatedly parse the original header.
## How to use it
The way to do it is to pass the header files you want precompiled to the `target_precompile_headers` command.
Imagine this folder structure:
```
.
├── CMakeLists.txt
└── src
├── header.h
└── main.cpp
1 directory, 3 files
```
Now we create a very basic CMakeLists.txt:
```cmake
cmake_minimum_required(VERSION 3.16)
project(MyProject)
set(HEADER_FILES
src/header.h)
set(SOURCE_FILES
src/main.cpp
${HEADER_FILES}
)
include_directories(src)
add_executable(MyProject ${SOURCE_FILES})
```
Notice we require a minimum cmake version of 3.16, this is due to `target_precompile_headers` being added in that version.
We add the command after the `add_executable` instruction to the private scope.
It's recommended to use the private scope to prevent precompiled headers appearing in an installed target, consumers should decide whether they want to use precompiled headers or not.
```cmake
target_precompile_headers(MyProject PRIVATE ${HEADER_FILES})
```
When compiling you can see now that it indeed compiles the headers:
```
Scanning dependencies of target MyProject
[ 33%] Building CXX object CMakeFiles/MyProject.dir/cmake_pch.hxx.gch
[ 66%] Building CXX object CMakeFiles/MyProject.dir/src/main.cpp.o
[100%] Linking CXX executable MyProject
[100%] Built target MyProject
```

View file

@ -0,0 +1,61 @@
+++
title = "Code conventions in DDraceNetwork"
description = "The code conventions enforced by DDraceNetwork."
date = 2020-11-28
+++
This is the part 2 of my series of articles about coding in DDraceNetwork, you can find the previous one [here](/blog/intro-to-ddnet).
## What are coding conventions?
They are a set of rules that dictate how the code should be written, so that the code style is consistent among the codebase.
## DDNet naming conventions
*Note: There is an ongoing discussion about variable naming, find out more [here](https://github.com/ddnet/ddnet/issues/2945).*
Currently, this is how we name things:
## Classes and structs
They are prefixed with `C` and followed by a capital letter, like `CController`, if the class is meant to be an interface it is prefixed by `I`.
## Enum constants
They must be all screaming snake case like: `MAX_PLAYERS`.
## Variable naming
The name is divided in 3 parts: qualifier, prefix and name.
Common qualifiers: `m` for member variables, `s` for static variables.
There is also `g` for global variables with external linkage.
If, the qualifier is not empty it is followed by an underscore.
Example: `ms_YourVariable`.
There are 2 common type prefixes: `p` for pointers, `a` for arrays and `fn` for functions, note that you can stack the prefixes for example in the case of a pointer to a pointer.
Example: `pMyPointer`, `aBuf`, `ppArgs`, `m_pCharacter`, `m_pfnMyCallback`, `m_papfnMyPointerToArrayOfCallbacks`
The first letter of the variable must be uppercase.
## Common idioms.
The following snippet is very common in ddnet code:
```cpp
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "number: %d", 2);
```
This is how we format strings, and it's used everywhere (str_format is defined in system.h).
I will add more here when I find/remember more.
## More to come
- [Implementing a chat command](/blog/chat-command-ddracenetwork)
- Implementing a rcon command
- Adding configuration options to the client.
- Modify the menus to show a label and a button/checkbox/slider for the previously added options.
- Add a new network packet.
- Add a new map tile.
- Any other idea I may get in the future.

View file

@ -0,0 +1,94 @@
+++
title = "An intro to the DDraceNetwork game source code"
description = "An introduction to the ddnet source code."
date = 2020-11-03
+++
## What is DDraceNetwork?
It's an open source mod of [teeworlds](https://teeworlds.com/) which was released on [steam](https://store.steampowered.com/app/412220/DDraceNetwork/) not long ago.
The language used is C++ along with some python scripts to generate the network protocol, it uses [CMake](https://cmake.org/) for building.
It's made on top of SDL2 with a custom OpenGL renderer.
The source can be located here: [github.com/ddnet/ddnet](https://github.com/ddnet/ddnet)
## My story on DDNet
This mod, also called ddnet was a breaking point on my path towards learning and improving my programming skills, since it introduced me to a codebase quite larger than what I was used to, if I'm honest, the first time I tried to do something in it, I was overwhelmed, but after some hardships and help from a [friend](https://timakro.de/) I got more used to it.
## The basic layout
The code of the game is located in the `src` directory, here I highlight the important directories it has:
### - `src/base`
This contains the system.h and system.c files, which is kind of an abstraction layer over the standard library, it also contains most platform specific code.
Here you can find functions for string formatting, memory and thread management and some cryptography stuff.
You will mostly never touch these files, but they are used a lot.
There is a subfolder in base named "tl", it contains an implementation for algorithm and array, but we are currently planning to move away from this.
### - `src/engine`
Here lives the code for anything that is not game specific (or kinda, it's not crystal clear actually), you can find the implementation for the graphics backend, sound, input, notifications, networking, console, SQL, etc.
If your objective is to make a mod, you will probably not touch this folder either.
### - `src/game`
Here you will find the code that implements the client and the server.
In the base folder you can find:
`collision.cpp`: Collision related code.
`ddracecommands.h`: Rcon commands.
`gamecore.cpp`:
Implements the physics, changing this means the community will cry at you because physics bugs are used in actual maps (so as we say, our physics have no bugs only features).
`layers.cpp`: Map layers.
`mapbugs.cpp`:
A try into fixing the physics "bugs", what it does is preserve the bug in specific maps that use them and fix them for new maps.
Currently, the only "bug" preserved with this is a double grenade explosion bug used in the map [binary](https://ddnet.tw/maps/Binary).
### - `src/game/client`
Client specific code, the client is made up of components, each component is a class that extends `CComponent`, with this, you can [access](https://github.com/ddnet/ddnet/blob/e256b11d367d001f0baf3905ab78e21ae2747718/src/game/client/component.h#L21) the kernel, graphics, text rendering, sound, console; basically most of the stuff implemented in the engine.
Components may also implement methods which will be called on a particular [event](https://github.com/ddnet/ddnet/blob/e256b11d367d001f0baf3905ab78e21ae2747718/src/game/client/component.h#L61), such as *OnRender*, *OnInit*, *OnInput*.
The file `gameclient.cpp` implements the game client, which has all the logic behind handling the components, receiving network [packets](https://github.com/ddnet/ddnet/blob/e256b11d367d001f0baf3905ab78e21ae2747718/src/game/client/gameclient.cpp#L752) and more.
### - `src/game/server`
Server specific code, it keeps track of everything, players and all the entities.
In `gamecontext.cpp` `CGameContext` is implemented, it's the heart of the server, it handles lots of stuff like chat, clients, map changes, network messages, commands, moderation, etc.
`CGameContext` is not only implemented in this file, it also has some parts implemented in `ddracecommands.cpp` for example.
The `gamecontroller.cpp` handles player spawns, some map entities, it also sends the gameinfo packet.
The`gameworld.cpp` tracks all the entities in the game.
In `player.cpp` you can find the class `CPlayer` that keeps track of a player, it stores any information related to the player that is not related to physics.
One of the most important entities is `CCharacter`, it keeps track of the physical representation of the player (we call them "tee(s)"), it's destroyed on death and created when spawning by `CPlayer`. There are also more entities like pickups, laser, projectile, etc...
## More to come
There is lot more stuff in here, and the best way to learn it is by actually implementing things.
I plan on making this a series of posts implementing stuff to the game (useful or not), here is a sneak peak of whats to come:
- [Code conventions and basic stuff](/blog/code-conventions-in-ddnet).
- [Implementing a chat command](/blog/chat-command-ddracenetwork)
- Implementing a rcon command
- Adding configuration options to the client.
- Modify the menus to show a label and a button/checkbox/slider for the previously added options.
- Add a new network packet.
- Add a new map tile.
- Any other idea I may get in the future.

View file

@ -0,0 +1,110 @@
+++
title = "An intro to MiniUPNP"
description = "An intro to the MiniUPNP C library."
date = 2020-04-14
+++
If you have a software that requires port forwarding, you may want to implement UPnP to make things easy for your end users.
When I wanted to implement it, the first library I found was [MiniUPnP](http://miniupnp.free.fr/files/), sadly it doesn't have much documentation but a quick look at the header files and some examples on the internet I managed to make it work, here is how:
First the required include directives we need:
```c
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#include <miniupnpc/upnperrors.h>
#include <stdio.h>
```
The first thing we need to do is discover the UPnP devices on the network, this is done using `upnpDiscover`:
```c
int main() {
struct UPNPDev *upnp_dev = 0;
int error = 0;
upnp_dev = upnpDiscover(2000, NULL, NULL, 0, 0, 2, &error);
if(error != 0) {
printf("error discovering upnp devices: %s\n", strupnperror(error));
return 1;
}
}
```
Then we need to retrieve the Internet Gateway Device we discovered:
```c
struct UPNPUrls upnp_urls;
struct IGDdatas upnp_data;
char aLanAddr[64];
const char *pPort = "8000";
// Retrieve a valid Internet Gateway Device
int status = UPNP_GetValidIGD(
upnp_dev,
&upnp_urls,
&upnp_data,
aLanAddr,
sizeof(aLanAddr)
);
printf("status=%d, lan_addr=%s\n", status, aLanAddr);
```
We check if we got the correct status and then map the port.
We also declare the port we want to use, in this case I use the same number for the internal and external ports:
```c
if (status == 1)
{
printf("found valid IGD: %s\n", upnp_urls.controlURL);
error =
UPNP_AddPortMapping(
upnp_urls.controlURL,
upnp_data.first.servicetype,
pPort, // external port
pPort, // internal port
aLanAddr, "My Application Name", "UDP",
0, // remote host
"0" // lease duration, recommended 0 as some NAT
// implementations may not support another value
);
if (error)
{
printf("failed to map port\n");
printf("error: %s\n", strupnperror(error));
}
else
printf("successfully mapped port\n");
}
else
printf("no valid IGD found\n");
```
Now the port is mapped, at this point you can open a socket bound to the internal port and external clients will be able to connect using the external port.
Then we do some cleanup:
*Note: The port here is the external one.*
```c
error = UPNP_DeletePortMapping(upnp_urls.controlURL,
upnp_data.first.servicetype,
pPort,
"UDP",
0);
if (error != 0) {
printf("port map deletion error: %s\n", strupnperror(error));
}
FreeUPNPUrls(&upnp_urls);
freeUPNPDevlist(upnp_dev);
return 0;
```
Complete source code: [gist](https://gist.github.com/edg-l/98241d2ef929661f0bb20136ebda16cd)
Compiled using: `cc -lminiupnpc upnp.c`

View file

@ -0,0 +1,36 @@
+++
title = "Modernize your linux workflow with Rust"
description = "Various tools and software that will modernize your workflow"
date = 2020-11-06
+++
## exa, a replacement for 'ls'
A replacement for ls which features better defaults and more features.
[github.com/ogham/exa](https://github.com/ogham/exa)
![exa example](/static/img/exa_preview.png)
## bat, a cat(1) clone with wings.
Bat supports syntax highlighting, git integration, paging and more.
![](/static/img/bat_cmd_example.png)
If you use (neo)vim with fzf.vim and have ripgrep and bat installed your search will have a preview window with highlighted code:
![](/static/img/vim_bat_preview.png)
## fd, a simple, fast and user-friendly alternative to find
[github.com/sharkdp/fd](https://github.com/sharkdp/fd)
It's faster, more convenient, ignores hidden files and gitignore files by default and more.
![](https://raw.githubusercontent.com/sharkdp/fd/master/doc/screencast.svg)
## Starship, a customizable cross-shell prompt
[starship.rs](https://starship.rs/)
![](https://raw.githubusercontent.com/starship/starship/master/media/demo.gif)

View file

@ -0,0 +1,46 @@
+++
title = "The Rust dbg! macro"
description = "A short article about a quite unknown but useful macro in Rust."
date = 2021-07-25
+++
The `dbg!` macro is a useful macro to debug, and I think a not well known one, not to be confused with debug logs using format strings, this macro is useful when you are about to put `println` calls everywhere in your code to know if it reached a path, what value a variable has, etc.
It uses the `Debug` trait implementation of the type of the given expression.
Since Rust is an expression oriented language, you can use this macro nearly everywhere:
```rust
fn factorial(n: u32) -> u32 {
if dbg!(n <= 1) {
dbg!(1)
} else {
dbg!(n * factorial(n - 1))
}
}
fn main() {
dbg!(factorial(4));
}
```
This outputs the following:
```
[src/main.rs:2] n <= 1 = false
[src/main.rs:2] n <= 1 = false
[src/main.rs:2] n <= 1 = false
[src/main.rs:2] n <= 1 = true
[src/main.rs:3] 1 = 1
[src/main.rs:5] n * factorial(n - 1) = 2
[src/main.rs:5] n * factorial(n - 1) = 6
[src/main.rs:5] n * factorial(n - 1) = 24
[src/main.rs:10] factorial(4) = 24
```
Using this macro without any argument will print the current file and line number.
One thing to be aware is that this macro moves the input, in cases where this is a problem it's useful to borrow.
Sources:
- https://doc.rust-lang.org/std/macro.dbg.html

View file

@ -0,0 +1,80 @@
+++
title = "Rust Iterators: Fibonacci series"
description = "Implementing an iterator that generates fibonacci numbers."
date = 2020-12-01
+++
In this article we will implement an iterator that generates Fibonacci numbers,
where each number is the sum of the preceding ones, starting with 0 and 1.
First we define our data structure:
```rust
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci {
a: 1,
b: 0
}
}
}
```
Here a and b represent the preceding numbers.
To implement the iterator we need to implement the `std::iter::Iterator` trait.
```rust
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
```
Thus, we have to import it:
```rust
use std::iter::Iterator;
```
We need to define the type Item and implement `next()`.
```rust
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let r = self.b;
self.b = self.a;
self.a += r;
Some(r)
}
}
```
Since fibonacci series are infinite, we always return Some(), but if you implement a non-infinite iterator you will have to return None at some point.
And then to see how it works:
```rust
fn main() {
let fib = Fibonacci::new();
// Take 20 fibonacci numbers and put them into a vector.
let result: Vec<u64> = fib.take(20).collect();
println!("{:?}", result);
}
```
Which outputs:
```
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
```

View file

@ -0,0 +1,90 @@
+++
title = "Setting Up SDL2 with CMake"
description = "How to setup and use SDL2 using the CMake build tool."
date = 2019-04-27
+++
## Installing CMake
Most common distributions have cmake available on their package manager repostories:
```bash
# Debian based
sudo apt install cmake
# Arch
pacman -S cmake
```
## Install SDL2 libraries
I only know about the debian based ones, if you are on another distro you should look them up.
```bash
sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev libsdl2-gfx-dev
```
## Directory Structure
Here is a common directory structure when using cmake to find packages:
```
├── cmake
│   ├── FindSDL2.cmake
│   ├── FindSDL2_mixer.cmake
│   └── FindSDL2_net.cmake
├── CMakeLists.txt
└── src
└── main.cpp
```
You can find the cmake files to find SDL2 and it's components
[here](https://github.com/aminosbh/sdl2-cmake-modules)
Or you can use this simple command:
```bash
cd cmake
wget https://raw.githubusercontent.com/aminosbh/sdl2-cmake-modules/master/FindSDL2{,_gfx,_image,_mixer,_net,_ttf}.cmake
```
## Creating the CMakeLists.txt file
```cmake
cmake_minimum_required(VERSION 3.13)
project(MyProject)
# Needed so that cmake uses our find modules.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
find_package(SDL2 REQUIRED)
find_package(SDL2_net REQUIRED)
find_package(SDL2_mixer REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_gfx REQUIRED)
find_package(SDL2_ttf REQUIRED)
set(SOURCE_FILES
src/main.cpp
)
include_directories(src)
add_executable(MyProject ${SOURCE_FILES})
target_link_libraries(MyProject SDL2::Main SDL2::Net SDL2::Mixer SDL2::Image SDL2::TTF SDL2::GFX)
```
And thats it! Now you can remove the SDL2 components you don't want to use.
## Building
Following the standard cmake procedure:
```bash
mkdir build
cd build
cmake ..
make -j${nproc}
```

View file

@ -0,0 +1,116 @@
+++
title = "What's new in Python 3.9"
description = "A list of major new features in python 3.9"
date = 2020-11-10
+++
## Python 3.9
Python 3.9 has been released on October 5, 2020.
## Add Union Operators To dict (PEP 584)
This allows the union operation to be performed on dicts:
```python
>>> a = {'x': 1, 'y': 2, 'z': 3}
>>> e = {'w': 'hello world'}
>>> a | e
{'x': 1, 'y': 2, 'z': 3, 'w': 'hello world'}
```
And also:
```python
>>> x = a
>>> x
{'x': 1, 'y': 2, 'z': 3}
>>> x |= e
>>> x
{'x': 1, 'y': 2, 'z': 3, 'w': 'hello world'}
```
## Type Hinting Generics In Standard Collections (PEP 585)
This feature enables type hinting using the standard collections without having to rely on the `typings` module.
Previously to type hint a list you would do:
```python
from typings import List
def somefunc(a: List[int]):
pass
```
Now you can use the standard type:
```python
def somefunc(a: list[int]):
pass
```
From this version, importing **collections** from `typings` is deprecated, and they will be removed in 5 years.
## Flexible function and variable annotations (PEP 593)
This feature adds a new type `Annotated` which allows us to extend type annotations with metadata.
This allows a type `T` to be annotated with metadata `x` like so:
```python
T1 = Annotated[T, x]
# E.g
UnsignedShort = Annotated[int, struct2.ctype('H')]
SignedChar = Annotated[int, struct2.ctype('b')]
# Multiple type annotations are supported
T2 = Annotated[int, ValueRange(3, 10), ctype("char")]
```
The metadata can then be used for static or runtime analysis with tools such as [mypy](http://www.mypy-lang.org/)
This feature allows authors to introduce new data types with graceful degradation,
for example if mypy doesn't know how to parse X Annotation it should just ignore its metadata and use the annotated type.
## Relaxing Grammar Restrictions On Decorators (PEP 614)
Python currently requires that all decorators consist of a dotted name, optionally followed by a single call. This PEP proposes removing these limitations and allowing decorators to be any valid expression.
An expression here means "anything that's valid as a test in if, elif, and while blocks".
Basically this:
```python
button_0 = buttons[0]
@button_0.clicked.connect
def spam():
pass
```
Can now be:
```python
@buttons[0].clicked.connect
def spam():
pass
```
## Support for the IANA Time Zone Database in the Standard Library
This feature adds a new module `zoneinfo` that provides a concrte time zone implementation supporting the IANA time zone database.
You can find more about this module here: [zoneinfo](https://docs.python.org/3/library/zoneinfo.html)
Example:
```python
>>> from zoneinfo import ZoneInfo
>>> from datetime import datetime, timedelta
>>> dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo("America/Los_Angeles"))
>>> print(dt)
2020-10-31 12:00:00-07:00
>>> dt.tzname()
'PDT'
```
## String methods to remove prefixes and suffixes
Adds two new methods, [removeprefix()](https://docs.python.org/3/library/stdtypes.html?highlight=removeprefix#str.removeprefix) and [removesuffix()](https://docs.python.org/3/library/stdtypes.html?highlight=removeprefix#str.removesuffix), to the APIs of Python's various string objects.

View file

@ -0,0 +1,131 @@
+++
title = "Wrapping errors in Rust"
description = "Wrap internal and external errors with your own error type."
date = 2021-01-24
+++
While I was developing a rust crate ([paypal-rs](https://github.com/edg-l/paypal-rs)) I noticed my error handling was pretty bad.
In that crate I had to handle 2 different types of errors:
- HTTP related errors, in this case `reqwest::Error`
- Paypal API errors, which I represent with my own struct `PaypalError`.
Initially I used [anyhow](https://github.com/dtolnay/anyhow) but then I found out this is pretty much only good to be used on binary applications, not in libraries.
The way to make this nice and clean for the library consumers is to wrap the errors.
## Wrapping the errors
First we need to know which errors need to be wrapped, in my case I have `PaypalError`:
```rust
/// A paypal api response error.
#[derive(Debug, Serialize, Deserialize)]
pub struct PaypalError {
// ...
}
// implement Error and Display for PaypalError...
```
And then an error from the reqwest library: `reqwest::Error`.
First we create an enum to represent all possible errors in our library:
```rust
#[derive(Debug)]
pub enum ResponseError {
/// paypal api error.
ApiError(PaypalError),
/// http error.
HttpError(reqwest::Error)
}
```
And as with any error, we have to implement `Error` and `fmt::Display`:
```rust
impl fmt::Display for ResponseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResponseError::ApiError(e) => write!(f, "{}", e),
ResponseError::HttpError(e) => write!(f, "{}", e),
}
}
}
impl Error for ResponseError {
// Implement this to return the lower level source of this Error.
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ResponseError::ApiError(e) => Some(e),
ResponseError::HttpError(e) => Some(e),
}
}
}
```
Now we should make all the functions that return a Result use the new Error type, this will also give consistency to all our functions.
However, right now we can't use `?` directly on our wrapped Errors:
```rust
/// func_call_returns_error() returns a Result<(), reqwest::Error>
pub fn some_func() -> Result<(), ResponseError> {
// Won't work, because the error returned is not ResponseError and has no From implementation!
// func_call_returns_error()?
// However we can map it.
func_call_returns_error().map_err(ResponseError::HttpError)?;
Ok(())
}
```
## Implementing From on the wrapped errors
To solve this, we have to implement `From<PaypalError>` and `From<reqwest::Error>`:
```rust
impl From<PaypalError> for ResponseError {
fn from(e: PaypalError) -> Self {
ResponseError::ApiError(e)
}
}
impl From<reqwest::Error> for ResponseError {
fn from(e: reqwest::Error) -> Self {
ResponseError::HttpError(e)
}
}
```
And now our code becomes like this:
```rust
pub fn some_func() -> Result<(), ResponseError> {
func_call_returns_error()?;
Ok(())
}
```
## The library to skip all this process
There is a library called [thiserror](https://github.com/dtolnay/thiserror), which implements macros to make this process a breeze, here is how our code ends up if we use this library:
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ResponseError {
/// A paypal api error.
#[error("api error {0}")]
ApiError(#[from] PaypalError),
/// A http error.
#[error("http error {0}")]
HttpError(#[from] reqwest::Error)
}
```
And that's all the code we need!
This is equal (or maybe even better) than our previous code, the best is that it is entirely transparent, the library consumers won't even know `thiserror` was used, quoting their github:
> Thiserror deliberately does not appear in your public API. You get the same thing as if you had written an implementation of std::error::Error by hand, and switching from handwritten impls to thiserror or vice versa is not a breaking change.

61
content/projects.md Normal file
View file

@ -0,0 +1,61 @@
+++
title = "Projects"
template_page = "page.html"
+++
All my projects are hosted at [github.com/edg-l](https://github.com/edg-l) and [git.edgarluque.com](https://git.edgarluque.com/)
## Rustack
An opinionated fullstack web template
[github.com/edg-l/rustack](https://github.com/edg-l/rustack)
## sitewriter
A rust library to write sitemaps
[github.com/edg-l/sitewriter](https://github.com/edg-l/sitewriter)
## DDraceNetwork Wiki website
A collaborative wiki that I host and maintain.
[wiki.ddnet.tw](https://wiki.ddnet.tw/)
## paypal-rs
A rust library that wraps the paypal api asynchronously.
[github.com/edg-l/paypal-rs](https://github.com/edg-l/paypal-rs)
## Unique clan website
A website for a game community / clan: [uniqueclan.net](https://uniqueclan.net/)
Made with nodejs, expressjs, mongodb and mysql.
[github.com/unique-clan](https://github.com/unique-clan)
## discord.aio
An asynchronous Discord API wrapper for python.
[github.com/edg-l/discord.aio](https://github.com/edg-l/discord.aio)
## Discordia
A spigot plugin that creates a chat bridge between discord and minecraft.
[github.com/edg-l/Discordia](https://github.com/edg-l/Discordia)
## Helperbot
A spigot plugin that adds a "bot" that will answer users questions without any command (using regex).
[github.com/edg-l/MC-HelperBot](https://github.com/edg-l/MC-HelperBot)
## Teeworlds Server Status
Get information about teeworlds / ddnet servers using typescript or javascript.
[github.com/edg-l/teeworlds-server-status](https://github.com/edg-l/teeworlds-server-status)
## A blog about programming in Catalan
[struct.cat](https://struct.cat/)

158
sass/style.scss Normal file
View file

@ -0,0 +1,158 @@
/*
1. Use a more-intuitive box-sizing model.
*/
*,
*::before,
*::after {
box-sizing: border-box;
font-size: 16px;
@media screen and (max-width: 500px) {
font-size: 12px;
}
@media screen and (max-width: 800px) {
font-size: 14px;
}
}
/*
2. Remove default margin
*/
* {
margin: 0;
}
/*
3. Allow percentage-based heights in the application
*/
html,
body {
height: 100%;
font-family: monospace;
}
/*
Typographic tweaks!
4. Add accessible line-height
5. Improve text rendering
*/
body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
/*
6. Improve media defaults
*/
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/*
7. Remove built-in form typography styles
*/
input,
button,
textarea,
select {
font: inherit;
}
/*
8. Avoid text overflows
*/
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/*
9. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}
$breakpoint: 900px;
.title {
font-size: 2em;
text-align: center;
}
h1 {
font-size: 1.6em;
}
h2 {
font-size: 1.4em;
}
h3 {
font-size: 1.2em;
}
h1,
h2,
h3 {
text-transform: uppercase;
}
p {
text-align: justify;
margin: 1em 0;
}
a {
color: black;
font-weight: bold;
}
pre {
padding: 10px;
overflow-x: auto;
border: 1px solid black;
margin: 1em 0;
}
blockquote {
border: 1px gray dashed;
padding: 5px 10px;
}
.container {
margin: 10px auto;
margin-top: 0;
padding-top: 10px;
max-width: $breakpoint;
@media screen and (max-width: $breakpoint) {
max-width: 100%;
margin: 10px 10px;
}
}
ul.nav {
text-align: right;
text-transform: uppercase;
li {
display: inline;
padding: 0 5px;
}
}
.brand-title {
font-size: 1.4rem;
text-transform: uppercase;
font-weight: bolder;
text-align: right;
margin: 0;
}
.blog-date {
text-align: right;
}

35
templates/base.html Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Edgar Luque" />
{% block seo %}
<meta name="description" content="My personal website." />
<meta property="og:title" content="Home" />
<meta property="og:description" content="My personal website." />
<meta property="og:type" content="article" />
<meta property="twitter:title" content="Home" />
<meta property="twitter:description" content="My personal website." />
{% endblock %}
<meta property="twitter:creator" content="@edgluq" />
<meta property="twitter:site" content="@edgluq" />
<title>{% block title %}Home{% endblock %} | Edgar Luque</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<header class="container">
<p class="brand-title">Edgar Luque's Website</p>
<ul class="nav">
<li><a href="/">Blog</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="https://github.com/edg-l">Github</a></li>
<li><a href="/blog/atom.xml">RSS</a></li>
</ul>
</header>
<section class="section">
<div class="container">{% block content %} {% endblock %}</div>
</section>
</body>
</html>

9
templates/blog-page.html Normal file
View file

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<h1 class="title">
{{ page.title }}
</h1>
<p class="blog-date"><strong>{{ page.date }}</strong></p>
{{ page.content | safe }}
{% endblock content %}

20
templates/index.html Normal file
View file

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block content %}
<h1 class="title">
Welcome to my website
</h1>
<p>This is my personal website, where I post articles and other stuff, usually about coding, ideas or anything related to the tech world.</p>
{% set section = get_section(path="blog/_index.md") %}
<h2>Articles</h2>
<div class="posts">
{% for page in section.pages %}
<article class="post">
<span>{{ page.date }}: </span>
<a href="{{ page.permalink }}" >{{ page.title }}</a>
</article>
{% endfor %}
</div>
{% endblock content %}

8
templates/page.html Normal file
View file

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<h1 class="title">
{{ page.title | safe }}
</h1>
{{ page.content | safe }}
{% endblock content %}