added more stuff
This commit is contained in:
		
							parent
							
								
									dc211342c3
								
							
						
					
					
						commit
						e0ed207a8d
					
				
							
								
								
									
										307
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										307
									
								
								README.md
									
									
									
									
									
								
							| @ -1,8 +1,115 @@ | ||||
| # My Code Styleguide | ||||
| # sneak/styleguide | ||||
| 
 | ||||
| The following is the first released version of my personal code styleguide. | ||||
| 
 | ||||
| Only the Go portion is "complete". The others are mostly just | ||||
| placeholders. | ||||
| 
 | ||||
| Feedback and suggestions are not only welcome but explicitly encouraged. | ||||
| 
 | ||||
| [sneak@sneak.berlin](mailto:sneak@sneak.berlin) | ||||
| 
 | ||||
| # My 2024 Code Styleguide | ||||
| 
 | ||||
| ## All | ||||
| 
 | ||||
| 1. Every project/repo should have a `Makefile` in the root. At a minimum, | ||||
|    `make clean`, `make run`, `make fmt`, and `make test` should work. Choose | ||||
|    a sane default target (`test` for libraries, `run` or `publish` for | ||||
|    binaries). `fmt` should invoke the appropriate formatters for the files | ||||
|    in the repo, such as `go fmt`, `prettier`, `black`, etc. Other standard | ||||
|    `Makefile` targets include `deploy`, `lint`. Consider the `Makefile` the | ||||
|    official documentation about how to operate the repository. | ||||
| 
 | ||||
| 1. If it's possible to write a `Dockerfile`, include at least a simple one. | ||||
|    It should be possible to build and run the project with `docker build .`. | ||||
| 
 | ||||
| 1. For F/OSS-licensed software, try to include the full source code of the | ||||
|    current version (and any dependencies, such as vendored dependencies) in | ||||
|    the docker image. They're small and should be included with the binary. | ||||
| 
 | ||||
| 1. Under no circumstances should any credentials or secrets ever be | ||||
|    committed to any repository, even private ones. Store secrets in | ||||
|    environment variables, and if they are absolutely required, check on | ||||
|    startup to make sure they are set/non-default and complain loudly if not. | ||||
|    Exception, sometimes: public keys. (Public keys can still sometimes be | ||||
|    secrets for operational security reasons.) | ||||
| 
 | ||||
| 1. Avoid nesting `if` statements. If you have more than one level of | ||||
|    nesting, consider inverting the condition and using `return` to exit | ||||
|    early. | ||||
| 
 | ||||
| 1. Almost all services/servers should accept their configuration via | ||||
|    environment variables. Only go full config file if absolutely necessary. | ||||
| 
 | ||||
| 1. For services/servers, log JSON to stdout. This makes it easier to parse | ||||
|    and aggregate logs when run under `docker`. Use structured logging | ||||
|    whenever possible. You may detect if the output is a terminal and | ||||
|    pretty-print the logs in that case. | ||||
| 
 | ||||
| 1. Debug mode is enabled by setting the environment variable `DEBUG` to a | ||||
|    non-empty string. This should enable verbose logging and such. It will | ||||
|    never be enabled in prod. | ||||
| 
 | ||||
| 1. For services/servers, make a healthcheck available at | ||||
|    `/.well-known/healthcheck`. This is out of spec but it is my personal | ||||
|    standard. This should return a 200 OK if the service is healthy, along | ||||
|    with a JSON object containing the service's name, uptime, and any other | ||||
|    relevant information, and a key of "status" with a value of "ok" if the | ||||
|    service is healthy. Make sure that in the event of a failure, the service | ||||
|    returns a 5xx status code for that route. | ||||
| 
 | ||||
| 1. If possible, for services/servers, include a /metrics endpoint that | ||||
|    returns Prometheus-formatted metrics. This is not required for all | ||||
|    services, but is a nice-to-have. | ||||
| 
 | ||||
| ## Bash / Shell | ||||
| 
 | ||||
| 1. Use `[[` instead of `[` for conditionals. It's a shell builtin and | ||||
|    doesn't have to execute a separate process. | ||||
| 
 | ||||
| 1. Use `$( )` instead of backticks. It's easier to read and nest. | ||||
| 
 | ||||
| 1. Use `#!/usr/bin/env bash` as the shebang line. This allows the script to | ||||
|    be run on systems where `bash` is not in `/bin`. | ||||
| 
 | ||||
| 1. Use `set -euo pipefail` at the top of every script. This will cause the | ||||
|    script to exit if any command fails, and will cause the script to exit if | ||||
|    any variable is used before it is set. | ||||
| 
 | ||||
| 1. Use `pv` for progress bars when piping data through a command. This makes | ||||
|    it easier to see how much data has been processed. | ||||
| 
 | ||||
| 1. Put all code in functions, even a main function. Define all functions | ||||
|    then call main at the bottom of the file. | ||||
| 
 | ||||
| ## JavaScript / ECMAScript / ES6 | ||||
| 
 | ||||
| 1. Use `const` for everything. If you need to reassign, use `let`. Never | ||||
|    use `var`. | ||||
| 
 | ||||
| 1. Use yarn for package management, avoid using npm. | ||||
| 
 | ||||
| 1. Use LTS node versions. | ||||
| 
 | ||||
| 1. Use `prettier` for code formatting, with four spaces for indentation. | ||||
| 
 | ||||
| 1. At a minimum, `npm run test` and `npm run build` should work (complete | ||||
|    the appropriate scripts in `package.json`). The `Makefile` should call | ||||
|    these, do not duplicate the scripts in the `Makefile`. | ||||
| 
 | ||||
| ## Docker Containers (for services) | ||||
| 
 | ||||
| 1. Use `runit` with `runsvinit` as the entrypoint for all containers. This | ||||
|    allows for easy service management and logging. In startup scripts | ||||
|    (`/etc/service/*/run`) in the container, put a `sleep 1` at the top of | ||||
|    the script to avoid spiking the cpu in the case of a fast-exiting process | ||||
|    (such as in an error condition). This also limits the maximum number of | ||||
|    error messages in logs to 86400/day. | ||||
| 
 | ||||
| ## Python | ||||
| 
 | ||||
| 1. Format all code with `black`. | ||||
| 1. Format all code with `black`, with four space indents. | ||||
| 
 | ||||
| 2. Put all code in functions. If you are writing a script, put the script | ||||
|    in a function called `main` and call `main()` at the end of the script | ||||
| @ -15,19 +122,75 @@ | ||||
| 
 | ||||
| ## Golang | ||||
| 
 | ||||
| 1.  Any project that has more than 2 or 3 modules should use the `uber/fx` DI | ||||
|     framework to keep things tidy. | ||||
| 1.  Try to hard wrap long lines at 77 characters or less. | ||||
| 
 | ||||
| 2.  Don't commit anything that hasn't been `go fmt`'d. The only exception is | ||||
| 1.  Don't commit anything that hasn't been `go fmt`'d. The only exception is | ||||
|     when committing things that aren't yet syntactically valid, which should | ||||
|     only happen pre-v0.0.1 or on a non-main branch. | ||||
|     only happen pre-v0.0.1 or on a non-`main` branch. | ||||
| 
 | ||||
| 3.  Even if you are planning to deal with only positive integers, use | ||||
| 1.  Even if you are planning to deal with only positive integers, use | ||||
|     `int`/`int64` types instead of `uint`/`uint64` types. This is for | ||||
|     consistency and compatibility with the standard library; it's better | ||||
|     than casting all the time. | ||||
| 
 | ||||
| 4.  Try to use zerolog for logging. It's fast and has a nice API. For | ||||
| 1.  Any project that has more than 2 or 3 modules should use the | ||||
|     `go.uber.org/fx` dependency injection framework to keep things tidy. | ||||
| 
 | ||||
| 1.  If you have to choose between readable and clever, opt for readable. | ||||
|     It's ok to make the code less concise or slightly less idiomatic if you | ||||
|     can keep it dead simple. | ||||
| 
 | ||||
| 1.  Embed the git commit hash into the binary and include it in startup logs | ||||
|     and in health check output. This is to make it easier to correlate | ||||
|     running instances with their code. Do not include build time or build | ||||
|     user, as these will make the build nondeterministic. | ||||
| 
 | ||||
|     Example relevant Makefile sections: | ||||
| 
 | ||||
|     Given a `main.go` like: | ||||
| 
 | ||||
|     ```go | ||||
|     package main | ||||
| 
 | ||||
|     import ( | ||||
|         "fmt" | ||||
|     ) | ||||
| 
 | ||||
|     var ( | ||||
|         Version   string | ||||
|         Buildarch string | ||||
|     ) | ||||
| 
 | ||||
|     func main() { | ||||
|         fmt.Printf("Version: %s\n", Version) | ||||
|         fmt.Printf("Buildarch: %s\n", Buildarch) | ||||
|     } | ||||
|     ``` | ||||
| 
 | ||||
|     ```make | ||||
|     VERSION := $(shell git describe --always --dirty) | ||||
|     BUILDARCH := $(shell uname -m) | ||||
| 
 | ||||
|     GOLDFLAGS += -X main.Version=$(VERSION) | ||||
|     GOLDFLAGS += -X main.Buildarch=$(BUILDARCH) | ||||
| 
 | ||||
|     # osx can't statically link apparently?! | ||||
|     ifeq ($(UNAME_S),Darwin) | ||||
|             GOFLAGS := -ldflags "$(GOLDFLAGS)" | ||||
|     endif | ||||
| 
 | ||||
|     ifneq ($(UNAME_S),Darwin) | ||||
|             GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)" | ||||
|     endif | ||||
| 
 | ||||
|     ./httpd: ./pkg/*/*.go ./internal/*/*.go cmd/httpd/*.go | ||||
|         go build -o $@ $(GOFLAGS) ./cmd/httpd/*.go | ||||
|     ``` | ||||
| 
 | ||||
| 1.  Avoid obvious footguns. For example, use range instead of for loops for | ||||
|     iterating. | ||||
| 
 | ||||
| 1.  Try to use zerolog for logging. It's fast and has a nice API. For | ||||
|     smaller/quick projects, the standard library's `log` package (and | ||||
|     specifically `log/slog`) is fine. In that case, log structured logs | ||||
|     whenever possible, and import `sneak.berlin/go/simplelog` to configure | ||||
| @ -46,79 +209,118 @@ | ||||
|     } | ||||
|     ``` | ||||
| 
 | ||||
| 5.  Write at least a single test to check compilation. The test file can be | ||||
|     empty, but it should exist. This is to ensure that `go test ./...` will | ||||
|     always function as a syntax check at a minimum. | ||||
| 1.  Commit at least a single test file to check compilation. The test file | ||||
|     can be empty, but it should exist. This is to ensure that `go test | ||||
| ./...` will always function as a syntax check at a minimum. | ||||
| 
 | ||||
| 6.  For anything beyond a simple script or tool, or anything that is going to | ||||
| 1.  Full TDD and coverage isn't that important, but when fixing a specific | ||||
|     bug, try to write a test that reproduces the bug before fixing it. This | ||||
|     will help ensure that the bug doesn't come back later, and crystallizes | ||||
|     the experience of discovering the bug and the resulting fix into the | ||||
|     repository's history. | ||||
| 
 | ||||
| 1.  For anything beyond a simple script or tool, or anything that is going to | ||||
|     run in any sort of "production" anywhere, make sure it passes | ||||
|     `golangci-lint`. | ||||
| 
 | ||||
| 7.  Write a `Dockerfile` for every repo, even if it only runs the tests and | ||||
| 1.  Write a `Dockerfile` for every repo, even if it only runs the tests and | ||||
|     linting. `docker build .` should always make sure that the code is in an | ||||
|     able-to-be-compiled state, linted, and any tests run. The Docker build | ||||
|     should fail if linting doesn't pass. | ||||
| 
 | ||||
| 8.  Include a `Makefile` with targets for at least `clean` and `test`. If | ||||
| 1.  Include a `Makefile` with targets for at least `clean` and `test`. If | ||||
|     there are multiple binaries, include a target for each binary. If there | ||||
|     are multiple binaries, include a target for `all` that builds all | ||||
|     binaries. | ||||
| 
 | ||||
| 9.  If you are writing a single-module library, `.go` files are okay in the | ||||
| 1.  If you are writing a single-module library, `.go` files are okay in the | ||||
|     repo root. | ||||
| 
 | ||||
| 10. If you are writing a multi-module project, put all `.go` files in a | ||||
| 1.  If you are writing a multi-module project, put all `.go` files in a | ||||
|     `pkg/` or `internal/` directory. This is to keep the root clean and to | ||||
|     make it easier to see what is a library and what is a binary. | ||||
| 
 | ||||
| 11. Binaries go in `cmd/` directories. Each binary should have its own | ||||
| 1.  Binaries go in `cmd/` directories. Each binary should have its own | ||||
|     directory. This is to keep the root clean and to make it easier to see | ||||
|     what is a library and what is a binary. Only package `main` files | ||||
|     should be in `cmd/*` directories. | ||||
| 
 | ||||
| 12. Keep the `main()` function as small as possible. | ||||
| 1.  Keep the `main()` function as small as possible. | ||||
| 
 | ||||
| 13. Keep the `main` package as small as possible. Move as much code as is | ||||
|     feasible to a library package. | ||||
| 1.  Keep the `main` package as small as possible. Move as much code as is | ||||
|     feasible to a library package, even if it's an internal one. `main` is | ||||
|     just an entrypoint to your code, not a place for implementations. | ||||
|     Exception: single-file scripts. | ||||
| 
 | ||||
| 14. HTTP HandleFuncs should be returned from methods or functions that need | ||||
| 1.  HTTP HandleFuncs should be returned from methods or functions that need | ||||
|     to handle HTTP requests. Don't use methods or our top level functions | ||||
|     as handlers. | ||||
| 
 | ||||
| 15. Provide a .gitignore file that ignores at least `*.log`, `*.out`, and | ||||
| 1.  Provide a .gitignore file that ignores at least `*.log`, `*.out`, and | ||||
|     `*.test` files, as well as any binaries. | ||||
| 
 | ||||
| 16. Constructors should be called `New()` whenever possible. | ||||
| 1.  Constructors should be called `New()` whenever possible. | ||||
|     `modulename.New()` works great if you name the packages properly. | ||||
| 
 | ||||
| 17. Don't make packages too big. Break them up. | ||||
| 1.  Don't make packages too big. Break them up. | ||||
| 
 | ||||
| 18. Don't make functions or methods too big. Break them up. | ||||
| 1.  Don't make functions or methods too big. Break them up. | ||||
| 
 | ||||
| 19. Use descriptive names for functions and methods. Don't be afraid to | ||||
| 1.  Use descriptive names for functions and methods. Don't be afraid to | ||||
|     make them a bit long. | ||||
| 
 | ||||
| 20. Use descriptive names for modules and filenames. Avoid generic names | ||||
| 1.  Use descriptive names for modules and filenames. Avoid generic names | ||||
|     like `server`. `util` is banned. | ||||
| 
 | ||||
| 21. Constructors should take a Params struct if they need more than 1-2 | ||||
| 1.  Constructors should take a Params struct if they need more than 1-2 | ||||
|     arguments. Positional arguments are an endless source of bugs and | ||||
|     should be avoided whenever possible. | ||||
| 
 | ||||
| 22. Use `context.Context` for all functions that need it. If you don't need | ||||
| 1.  Use `context.Context` for all functions that need it. If you don't need | ||||
|     it, you can pass `context.Background()`. Anything long-running should | ||||
|     get and abide by a Context. A context does not count against your | ||||
|     number of function or method arguments for purposes of calculating | ||||
|     whether or not you need a Params struct, because the `ctx` is always | ||||
|     first. | ||||
| 
 | ||||
| 23. Contexts are always named `ctx`. | ||||
| 1.  Contexts are always named `ctx`. | ||||
| 
 | ||||
| 24. Use `context.WithTimeout` or `context.WithDeadline` for any function | ||||
| 1.  Use `context.WithTimeout` or `context.WithDeadline` for any function | ||||
|     that could potentially run for a long time. This is especially true for | ||||
|     any function that makes a network call. Sane timeouts are essential. | ||||
| 
 | ||||
| 25. Avoid global state, especially global variables. If you need to store | ||||
| 1.  If a structure/type is only used in one function or method, define it | ||||
|     there. If it's used in more than one, define it in the package. Keep it | ||||
|     close to its usages. For example: | ||||
| 
 | ||||
|     ```go | ||||
|     func (m *Mothership) tvPost() http.HandlerFunc { | ||||
| 
 | ||||
|         type MSTVRequest struct { | ||||
|                 URL string `json:"URL"` | ||||
|         } | ||||
| 
 | ||||
|         type MSTVResponse struct { | ||||
|         } | ||||
| 
 | ||||
|         return func(w http.ResponseWriter, r *http.Request) { | ||||
|             // parse json from request | ||||
|             var reqParsed MSTVRequest | ||||
|             err = json.NewDecoder(r.Body).Decode(&reqParsed) | ||||
|             ... | ||||
| 
 | ||||
|             if err != nil { | ||||
|                     SendErrorResponse(w, MSGenericError) | ||||
|                     return | ||||
|             } | ||||
| 
 | ||||
|             log.Info().Msgf("Casting to %s: %s", tvName, streamURL) | ||||
|             SendSuccessResponse(w, &MSTVResponse{}) | ||||
|         } | ||||
|     } | ||||
|     ``` | ||||
| 
 | ||||
| 1.  Avoid global state, especially global variables. If you need to store | ||||
|     state that is global to your launch or application instance, use a | ||||
|     package `globals` or `appstate` with a struct and a constructor and | ||||
|     require it as a dependency in your constructors. This will allow | ||||
| @ -127,10 +329,10 @@ | ||||
|     graph allows for it, put it in the main struct/object of your | ||||
|     application, but remember that this harms testability. | ||||
| 
 | ||||
| 26. Package-global "variables" are ok if they are constants, such as static | ||||
| 1.  Package-global "variables" are ok if they are constants, such as static | ||||
|     strings or integers or errors. | ||||
| 
 | ||||
| 27. Whenever possible, avoid hardcoding numbers or values in your code. Use | ||||
| 1.  Whenever possible, avoid hardcoding numbers or values in your code. Use | ||||
|     descriptively-named constants instead. Recall the famous SICP quote: | ||||
|     "Programs must be written for people to read, and only incidentally for | ||||
|     machines to execute." Rather than comments, a descriptive constant name | ||||
| @ -149,39 +351,39 @@ | ||||
|      } | ||||
|     ``` | ||||
| 
 | ||||
| 28. Define your struct types near their constructors. | ||||
| 1.  Define your struct types near their constructors. | ||||
| 
 | ||||
| 29. Define your interface types near the functions that use them, or if you | ||||
| 1.  Define your interface types near the functions that use them, or if you | ||||
|     have multiple conformant types, put the interface(s) in their own file. | ||||
| 
 | ||||
| 30. Define errors as package-level variables. Use a descriptive name for the | ||||
| 1.  Define errors as package-level variables. Use a descriptive name for the | ||||
|     error. Use `errors.New` to create the error. If you need to include | ||||
|     additional information in the error, use a struct that implements the | ||||
|     `error` interface. | ||||
| 
 | ||||
| 31. Use lowerCamelCase for local function/variable names. Use UpperCamelCase | ||||
| 1.  Use lowerCamelCase for local function/variable names. Use UpperCamelCase | ||||
|     for type names, and exported function/variable names. Use snake_case for | ||||
|     JSON keys. Use lowercase for filenames. | ||||
| 
 | ||||
| 32. Explicitly specify UTC for datetimes unless you have a very good reason | ||||
| 1.  Explicitly specify UTC for datetimes unless you have a very good reason | ||||
|     not to. Use `time.Now().UTC()` to get the current time in UTC. | ||||
| 
 | ||||
| 33. String dates should always be ISO8601 formatted. Use `time.Time.Format` | ||||
| 1.  String dates should always be ISO8601 formatted. Use `time.Time.Format` | ||||
|     with `time.RFC3339` to get the correct format. | ||||
| 
 | ||||
| 34. Use `time.Time` for all date and time values. Do not use `int64` or | ||||
| 1.  Use `time.Time` for all date and time values. Do not use `int64` or | ||||
|     `string` for dates or times internally. | ||||
| 
 | ||||
| 35. When using `time.Time` in a struct, use a pointer to `time.Time` so that | ||||
| 1.  When using `time.Time` in a struct, use a pointer to `time.Time` so that | ||||
|     you can differentiate between a zero value and a null value. | ||||
| 
 | ||||
| 36. Use `time.Duration` for all time durations. Do not use `int64` or | ||||
| 1.  Use `time.Duration` for all time durations. Do not use `int64` or | ||||
|     `string` for durations internally. | ||||
| 
 | ||||
| 37. When using `time.Duration` in a struct, use a pointer to `time.Duration` | ||||
| 1.  When using `time.Duration` in a struct, use a pointer to `time.Duration` | ||||
|     so that you can differentiate between a zero value and a null value. | ||||
| 
 | ||||
| 38. Whenever possible, in argument types and return types, try to use | ||||
| 1.  Whenever possible, in argument types and return types, try to use | ||||
|     standard library interfaces instead of concrete types. For example, use | ||||
|     `io.Reader` instead of `*os.File`. Tailor these to the needs of the | ||||
|     specific function or method. Examples: | ||||
| @ -227,6 +429,7 @@ | ||||
| 
 | ||||
|             -   `fmt.Stringer` is an interface for types that can convert | ||||
|                 themselves to a string. Any type that implements the `String() | ||||
| 
 | ||||
|     string` method satisfies this interface. | ||||
| 
 | ||||
|         -   **`error`** instead of custom error types: | ||||
| @ -277,15 +480,15 @@ string` method satisfies this interface. | ||||
|                 flags. Implementing the `String` and `Set` methods allows you to | ||||
|                 use custom types with the `flag` package. | ||||
| 
 | ||||
| 39. Avoid using `panic` in library code. Instead, return errors to allow | ||||
| 1.  Avoid using `panic` in library code. Instead, return errors to allow | ||||
|     the caller to handle them. Reserve `panic` for truly exceptional | ||||
|     conditions. | ||||
| 
 | ||||
| 40. Use `defer` to ensure resources are properly cleaned up, such as | ||||
| 1.  Use `defer` to ensure resources are properly cleaned up, such as | ||||
|     closing files or network connections. Place `defer` statements | ||||
|     immediately after resource acquisition. | ||||
| 
 | ||||
| 41. When calling a function with `go`, wrap the function call in an | ||||
| 1.  When calling a function with `go`, wrap the function call in an | ||||
|     anonymous function to ensure it runs in the new goroutine context: | ||||
| 
 | ||||
|     Right: | ||||
| @ -302,7 +505,7 @@ string` method satisfies this interface. | ||||
|     go someFunction(arg1, arg2) | ||||
|     ``` | ||||
| 
 | ||||
| 42. Use `iota` to define enumerations in a type-safe way. This ensures that | ||||
| 1.  Use `iota` to define enumerations in a type-safe way. This ensures that | ||||
|     the constants are properly grouped and reduces the risk of errors. | ||||
| 
 | ||||
|     Example: | ||||
| @ -343,7 +546,7 @@ string` method satisfies this interface. | ||||
|     ) | ||||
|     ``` | ||||
| 
 | ||||
| 43. Don't hardcode big lists of things in your normal code. Either isolate | ||||
| 1.  Don't hardcode big lists of things in your normal code. Either isolate | ||||
|     lists in their own module/package and write some getters, or use a third | ||||
|     party library. For example, if you need a list of country codes, you can | ||||
|     use | ||||
| @ -355,7 +558,7 @@ string` method satisfies this interface. | ||||
|     probably too much. Compress the file before embedding and uncompress | ||||
|     during the reading/parsing step for efficiency. | ||||
| 
 | ||||
| 44. When storing numeric values that represent a number of units, either | ||||
| 1.  When storing numeric values that represent a number of units, either | ||||
|     include the unit in the variable name (e.g. `uptimeSeconds`, | ||||
|     `delayMsec`, `coreTemperatureCelsius`), or use a type alias (that | ||||
|     includes the unit name), or use a 3p library such as | ||||
| @ -364,7 +567,7 @@ string` method satisfies this interface. | ||||
|     [github.com/bcicen/go-units](https://github.com/bcicen/go-units) for | ||||
|     temperatures (and others). The type system is your friend, use it. | ||||
| 
 | ||||
| 45. Once you have a working program, run `go mod tidy` to clean up your | ||||
| 1.  Once you have a working program, run `go mod tidy` to clean up your | ||||
|     `go.mod` and `go.sum` files. Tag a v0.0.1 or v1.0.0. Push your `main` | ||||
|     branch and tag(s). Subsequent work should happen on branches so that | ||||
|     `main` is "always releasable". "Releasable" in this context means that | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user