Contribute to this Epitech project involves following strict rules to keep a clean project and easily maintainable code.
- 1 - Project architecture and file naming
- 2 - Commits and Pull Requests
- 3 - Variables and types naming
- 4 - Header files and preprocessor directives
- 5 - Functions
- 6 - Documentation
Files conventions naming and project architecture need to be followed.
Files and folders names must be short and readable by architecure depth level.
Examples
-
File located in
./sources/display/prompt.c
can be read easily as:A file used to display prompt
-
File located in
./sources/display/builtins/cd.c
can be read easily as:A file used to display the builtin cd
The architecture of have to be organized as the follwing tree.
.
βββ CONTRIBUTING.md
βββ Makefile
βββ README.md
βββ includes
β βββ types β
This folder is reserved for types
β β β headers. This folder is normally symetric
β β β with sources/types folder (1st level only)
β β β
β β βββ shell β
Reserved folder for shell type
β β β βββ defs.h β
Reserved file to declare types and constants
β β β β relative to shell type
β β β βββ shell.h β
Reserved file to declare all functions
β β β relative to shell type
β β β β Other file here is not really good
β β βββ list
β β βββ defs.h
β β βββ list.h
β βββ display.h β
Reserved folder for sources/display
β β folder
β βββ ... π Here you can add other folders and sub folders
β to orgnanize inclusions, but max sub folders
β level is 2. Try to keep symetry with sources
β folder.
βββ sources
β βββ main.c
β βββ display β
Folder dedicated for all needed displays
β β βββ prompt.c β
File used to implement prompt display
β β β with at least function display_prompt
β β β
β β βββ builtins
β β βββ cd.c β
File used to implement cd builtin display
β β with at least function display_builtin_cd
β β
β βββ types β
This folder is reserved for types
β β implmementation. All functions and files
β β in this folder must not call functions outside
β β of types folder.
β β
β βββ list β
Folder of a type
β β βββ new.c β
File reserved to create a new instance
β β β of given type
β β βββ free.c β
File reserved to create a free given instance
β β β of type
β β βββ my_method.c β
File used to create a pseudo-method associated
β β to current type
β β
β βββ shell β
Folder dedicated to shell type (shell_t)
β β βββ builtins β
Sub-folder builtins that is not for a sub type
β β β β but for sub-methods
β β β βββ init.c β
Sub method : shell_builtins_init
β β β βββ builtins_remove.c β Bad group convention: useless prefix "builtins"
β β β β name it remove.c instead
β β β βββ register.c β
Sub method : shell_builtins_register
β β β
β β βββ env β
Sub-folder bultins that is not a sub type
β β β but sub-methods
β β βββ get.c
β β βββ serialize.c
β β βββ set.c β
Sub methods : shell_env_set and shell_env_unset
β β β because theses sub methods are linked : unset
β β β is set to non-existant
β β βββ set_specials.c β
Extension of set.c used to define particular
β β β cases
β β βββ unserialize.c
β βββ create.c β Bad group convention: new.c has to
β β be used instead
β βββ destroy.c β Bad group convention: free.c has
β to be used instead
βββ tests
The group convention is to use emojis with a past tense in your commits message and in your pull requests too.
β¨
New global feature
π¨
Utils functions
π‘
Display functions
π
Files functions
π
Types functions
β‘οΈ
Optimisation / Performance
ποΈ
Code architecture
π±
Assets
β
Tests
π
Files structure / File rename
π¦
Library
π
Gitignore
π
Coding-style
π¬
Documentation
π·οΈ
Header definition
π
Makefile / CMakeLists
π·ββοΈ
CI Github Action
π
Simple fix
π
Automation fix
ποΈ
Hotfix
π₯
Remove file / function
[EMOJI] [Past tense verb] [Description]
Examples :
-
π Fixed parsing of arguments
-
π·ββοΈ Added coding-style CI
-
β Updated units tests of files functions (criterion)
-
π¦οΈ Updated my_str_to_word_array function
An unified types and variable naming will allow us to keep our project clean and clear for all of us. All details of this sections are importants !
You have to declare your types in a C file header such the Epitech standard is imposing us.
A type in C have to be prototyped as follwing:
// The type name has to end with letter t and have the letter t.
// We have to avoid the most possible abbreviations
// Name have to be prefixed by first letter of existant used type
// -------
// v v
typedef union u_node_data {
char *str;
} node_data_t;
typedef struct s_shell {
char **env;
} env_t;
// ^
// The type name has to end with letter t and have the letter t.
// We have to avoid the most possible abbreviations
Enumerations needs to be prefixed by at least 2 letters to identify clearly its values and theses letters must relate to enumeration name. All values must be in uppercase.
// ST prefix is related to state_t (2 first letters of name)
typedef enum e_state {
ST_LOADING,
ST_VALID,
ST_ERROR
} state_t;
Sometimes just add an s
at the end of a name can do a big difference.
If you variable (not the type of it) contains several elements end it by an s
. Otherwise leave it singular.
Example
list_t *animals = list_new(); //Here there are several animals in current variable
node_t *animal = NULL; //But here we have just one of them
It the same rule for the names that you give to your types.
Examples
In the follwing exemple you must not set an s
at the end of type name. In fact, you're defining a type that describe just one state at time. Even if the enum
contains several values, the type is used to set just one state at time so you don't have to add s
.
typedef enum e_state {
ST_LOADING,
ST_VALID,
ST_ERROR
} state_t;
In the follwing exemple you have to set an s
at the end of type name because you're defining a type that contains several values in same time so you have to add an s
at the end.
typedef struct s_components {
button_t *button_start;
input_t *input_zone;
} components_t;
When you create a C header file you have first to follow the Epitech standard.
The header guard name needs to be like <NAME>_H_
.
#ifndef FILE_H_
#define FILE_H_
#endif /* !FILE_H_ */
In your header you have to indent all preprocessor directives. A preprocessor directive is a line which start with #
(#include
, #define
...).
Just after header guard definition, you have to add a break line. Preprocessor directives order is free, but all #include
directives have to be placed first.
Examples
β Correct
#ifndef FILE_H_
#define FILE_H_
#include <stdlib.h>
#define ABS(x) (x < 0 ? -x : x)
#define SQUARE(x) (x * x)
#endif /* !FILE_H_ */
β Incorrect
#ifndef FILE_H_
#define FILE_H_
#include <stdlib.h>
#define ABS(x) (x < 0 ? -x : x)
#define SQUARE(x) (x * x)
#endif /* !FILE_H_ */
β Incorrect
#ifndef FILE_H_
#define FILE_H_
#define ABS(x) (x < 0 ? -x : x)
#define SQUARE(x) (x * x)
#include <stdlib.h>
#endif /* !FILE_H_ */
When you're including headers, the inclusion order is:
- Inclusion from path (with chevrons :
<stdlib.h>
) - Inclusion from own files (with quotes :
"my.h"
) - Shortest directive at top
β Correct
#ifndef MY_HEADER_H_
#define MY_HEADER_H_
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "my.h"
#include "cjson.h"
#define ABS(x) (x < 0 ? -x : x)
#define SQUARE(x) (x * x)
#endif /* !MY_HEADER_H_ */
β Correct
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "my.h"
#include "types/list/defs.h"
β Incorrect
#ifndef MY_HEADER_H_
#define MY_HEADER_H_
#include <sys/wait.h>
#include <stdlib.h>
#include "my.h"
#include <sys/types.h>
#include "cjson.h"
#endif /* !MY_HEADER_H_ */
You have to name your macros and your defined values with the prefix of current context. This in order to avoid conflicts of naming.
Examples
- If you define the header guard of
includes/types/shell/shell.h
you have to name it :SHELL_H_
- If you define the header guard of
includes/types/shell/defs.h
you have to name it :SHELL_DEFS_H_
. - If you define the header guard of
includes/display.h
you have to name it :DISPLAY_H_
- If you define the header guard of
includes/display/builtins.h
you have to name it :DISPLAY_BUILTINS_H_
- If you define a macro in
includes/types/shell/defs.h
you have to name it :SHELL_EXIT_CODE
.
You have to set macro on 2 lines maximum.
If your macros are variables based, you have to pass these variables as parameters of your macro and you must not hard code it with the variables names. In fact in different context your variables could be undefined.
Example
//β Incorrect #define LIST_NODE_ITEM node->next.data->item //Will fail if node is undefined //β Correct #define LIST_NODE_ITEM(node) node->next.data->item
In your C functions you can return any value. But in some cases the returned value is just an indicator to specify if an error occured.
-
So in the most cases return value are
success
orfail
and to indicate them you have to prototype your functions with returned typebool
. In fact in order to make the developer read more easier we can use the created function in a pseudo-sentences like:bool add_item(int e, list_t *list) { //... (void) e; (void) list; if (success) return true; //No error occured else return false; //An error occured } //β Good convention //Next statement could be easily read as 'If non item add' or 'If item not added' then... if (!add_item(3, NULL)) printf("error during item adding\n"); //β Bad convention //Next statement can not be easily read : 'If item was added then error' => illogical if (add_item(3, NULL)) printf("error during item adding\n");
-
But in some others cases the value of returned code is useful. Then in that case, on
success
you have to return0
and allerrors
code can be described by other values :#include <string.h> int display_check_data(char *data) { if (!data) return 1; //To indicate that pointer is NULL (Error 1) if (strlen(data) == 0) return 2; //To indicate data is too short (Error 2) return 0; //To indicate success } //β Good convention int check_data_status = display_check_data("Hello"); if (check_data_status == 1) printf("NULL pointer given\n"); else if (check_data_status == 2) printf("Too short data given\n"); else printf("Valid data\n");
All functions that are using malloc
needs to have a symetric function to free allocated memory.
If this rule is not followed, your reviews could be refused.
Example
If you create
shell_new()
that allocate 2 strings inside ashell_t
structure (also allocated), you have to free all of them with symetric functionshell_free()
in which you will first free 2 strings and then theshell_t
structure.
If a conditionnal block contains only one line, you have to ommit brackets on it, but only if the block condition is on one, and only one line.
On a block of multiples conditions as if > else if > else
, if one on condition needs brackets you have to put brackets on all conditions of block.
Examples
// β Correct if (true) printf("Hello"); // β Incorrect (the conditionnal block is not on just one line) if (true && strlen("Super") == 5 && player.active) printf("Hello");// β Incorrect if (get_status(player) == SLEEPING) printf("Sleeping"); else if (true && strlen("Super") == 5 && player.active) printf("Hello"); // β Correct if (get_status(player) == SLEEPING) { printf("Sleeping"); } else if (strlen("Super") == 5 && player.active) { printf("Hello"); player.active = false; }
When your are using loops, there are 2 differents cases:
- You know how many loops you will do even before looping
- You don't know how many loops you will do before looping
So in the first case you have to use a for
loop. Otherwise you have to use while
loop.
Examples
-
for
loop:// β Correct // FOR loop is adapted because before entering in loop, we know how many loopings we are going to do. char *str = "Hello\n" size_t len = strlen(str); for (size_t i = 0; i < len; i++) putchar(str[i]); // β Incorrect // FOR loop is more adapted because before entering in loop, we know how many loopings we are going to do. char *str = "Hello\n" size_t len = strlen(str); size_t i = 0; while(i < len) { putchar(str[i]); i += 1; }
-
while
loop:// β Incorrect // FOR loop is not adapted because before entering in loop, we don't know how many loopings we are going to do. char *str = "Hello\n" char expected = 'l'; bool found = false; size_t len = strlen(str); for (size_t i = 0; i < len && !found; i++) { if (str[i] == expected) found = true; } // β Correct // WHILE loop is adapted because before entering in loop, we don't know how many loopings we are going to do. char *str = "Hello\n" char expected = 'l'; bool found = false; size_t len = strlen(str); while (i < len && !found) { if (str[i] == expected) found = true; i += 1; }
You have to document your code precisely to allow to others members of the group to understand easily your code. There is serveral manners to document your code in C langage, but we are going to use the Doxygen syntax. All documentation that you will write, needs to be placed in header files.
Doxygen is a software that allow to produce a complete documentation by usage of sepcial comment syntax. To write easily Doxygen comments you can download the extension corresponding to your IDE (exemple: Visual Studio Code, ...).
With the Doxygen extension, you can easily document a function for example, by typing : /***/
(just above your function declaration) and then pressing Enter
.
All tags
(@param
, @brief
...) needs to be followed by a description.
Rules for main tags :
@brief
: needs to be followed by a description of current function. This description must consist of one or more sentences beginning with a capital letter and ending with a period.@param
: describe the parameter that follow it, and have to begin with a capital letter and end without period.@return
: describe the returned value and have to follow same rules than@param
and must not contains type of returned value. If first word of description is an absolute value likefalse
for exemple, you have to ommit the first capital letter.
Example
/**
* @brief Count the length of given string.
* @param str String of which get length
* @return Length of given string
*/
size_t strlen(char *str);
/**
* @brief Check if given shell is in tty mode.
* @param shell Shell object of which check tty mode
* @return true if shell is in tty mode, false otherwise
*/
bool shell_in_tty_mode(shell_t *shell);
You can also add some tags like @info
, @warning
or @deprecated
to specify additionals informations on current function. These tags follow same rules than @brief
.
You have to document your all typedef
. Below, there are exemples for differents typedef
.
- Structure:
// Represent a shell with all necessary data typedef struct s_shell { char **env; //Environnement variables bool is_tty; //Specify if current shell is in tty mode } shell_t;
- Enumeration:
// Represent state of a shell typedef enum e_shell_state { SHST_RUNNING, //Shell running SHST_EXITED, //Shell exited } shell_state_t;
- Union:
// Represent the data contained in a node typedef union u_node_data { void *v_ptr; //Any pointer int v_int; //Integer value float v_float; //Float value } node_data_t;
- Function:
// Prototype of a shell builtin function typedef int (*shell_builtin_t)(int argc, char **argv, char **env);
More generally you can document your typedef above your typedef declaration.