diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..dea35c3 --- /dev/null +++ b/config.toml @@ -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 diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..c2e059d --- /dev/null +++ b/content/blog/_index.md @@ -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"] ++++ \ No newline at end of file diff --git a/content/blog/chat-command-ddracenetwork.md b/content/blog/chat-command-ddracenetwork.md new file mode 100644 index 0000000..f8165a9 --- /dev/null +++ b/content/blog/chat-command-ddracenetwork.md @@ -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 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. diff --git a/content/blog/cmake-precompiled-headers.md b/content/blog/cmake-precompiled-headers.md new file mode 100644 index 0000000..0d45eb8 --- /dev/null +++ b/content/blog/cmake-precompiled-headers.md @@ -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 +``` diff --git a/content/blog/code-conventions-in-ddnet.md b/content/blog/code-conventions-in-ddnet.md new file mode 100644 index 0000000..5a9a692 --- /dev/null +++ b/content/blog/code-conventions-in-ddnet.md @@ -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. \ No newline at end of file diff --git a/content/blog/intro-to-ddnet.md b/content/blog/intro-to-ddnet.md new file mode 100644 index 0000000..0f05ecf --- /dev/null +++ b/content/blog/intro-to-ddnet.md @@ -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. \ No newline at end of file diff --git a/content/blog/intro-to-upnp.md b/content/blog/intro-to-upnp.md new file mode 100644 index 0000000..78635a1 --- /dev/null +++ b/content/blog/intro-to-upnp.md @@ -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 +#include +#include +#include +``` + +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` \ No newline at end of file diff --git a/content/blog/modernize-your-tools.md b/content/blog/modernize-your-tools.md new file mode 100644 index 0000000..55aa2b9 --- /dev/null +++ b/content/blog/modernize-your-tools.md @@ -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) diff --git a/content/blog/rust-dbg-macro.md b/content/blog/rust-dbg-macro.md new file mode 100644 index 0000000..78e5ca9 --- /dev/null +++ b/content/blog/rust-dbg-macro.md @@ -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 \ No newline at end of file diff --git a/content/blog/rust-iterator-fibonacci.md b/content/blog/rust-iterator-fibonacci.md new file mode 100644 index 0000000..6ca63e2 --- /dev/null +++ b/content/blog/rust-iterator-fibonacci.md @@ -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; +} +``` + +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 { + 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 = 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] +``` + diff --git a/content/blog/sdl2-cmake.md b/content/blog/sdl2-cmake.md new file mode 100644 index 0000000..c511269 --- /dev/null +++ b/content/blog/sdl2-cmake.md @@ -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} +``` \ No newline at end of file diff --git a/content/blog/whats-new-in-python-3.9.md b/content/blog/whats-new-in-python-3.9.md new file mode 100644 index 0000000..b77242c --- /dev/null +++ b/content/blog/whats-new-in-python-3.9.md @@ -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. \ No newline at end of file diff --git a/content/blog/wrapping-errors-in-rust.md b/content/blog/wrapping-errors-in-rust.md new file mode 100644 index 0000000..7e1e13a --- /dev/null +++ b/content/blog/wrapping-errors-in-rust.md @@ -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` and `From`: + +```rust +impl From for ResponseError { + fn from(e: PaypalError) -> Self { + ResponseError::ApiError(e) + } +} + +impl From 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. \ No newline at end of file diff --git a/content/projects.md b/content/projects.md new file mode 100644 index 0000000..82842f1 --- /dev/null +++ b/content/projects.md @@ -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/) \ No newline at end of file diff --git a/sass/style.scss b/sass/style.scss new file mode 100644 index 0000000..535cf5c --- /dev/null +++ b/sass/style.scss @@ -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; +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..60015f8 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,35 @@ + + + + + + + {% block seo %} + + + + + + + {% endblock %} + + + {% block title %}Home{% endblock %} | Edgar Luque + + + + +
+

Edgar Luque's Website

+ +
+
+
{% block content %} {% endblock %}
+
+ + diff --git a/templates/blog-page.html b/templates/blog-page.html new file mode 100644 index 0000000..518de4d --- /dev/null +++ b/templates/blog-page.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} +

+ {{ page.title }} +

+

{{ page.date }}

+{{ page.content | safe }} +{% endblock content %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5019c46 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +

+ Welcome to my website +

+

This is my personal website, where I post articles and other stuff, usually about coding, ideas or anything related to the tech world.

+ +{% set section = get_section(path="blog/_index.md") %} +

Articles

+
+ {% for page in section.pages %} + + {% endfor %} +
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..ca1f458 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +

+ {{ page.title | safe }} +

+{{ page.content | safe }} +{% endblock content %} \ No newline at end of file