mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	docs: Add syntax highlighting languages to code blocks.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							8fd89f87e0
						
					
				
				
					commit
					b29b6f6526
				
			@@ -29,7 +29,7 @@ File not found errors (404) are served using a Django URL, so that we
 | 
			
		||||
can use configuration variables (like whether the user is logged in)
 | 
			
		||||
in the 404 error page.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```nginx
 | 
			
		||||
location /static/ {
 | 
			
		||||
    alias /home/zulip/prod-static/;
 | 
			
		||||
    # Set a nonexistent path, so we just serve the nice Django 404 page.
 | 
			
		||||
@@ -61,10 +61,7 @@ in
 | 
			
		||||
[the directory structure doc](../overview/directory-structure.md).
 | 
			
		||||
 | 
			
		||||
The main Zulip Django app is `zerver`. The routes are found in
 | 
			
		||||
```
 | 
			
		||||
zproject/urls.py
 | 
			
		||||
zproject/legacy_urls.py
 | 
			
		||||
```
 | 
			
		||||
`zproject/urls.py` and `zproject/legacy_urls.py`.
 | 
			
		||||
 | 
			
		||||
There are HTML-serving, REST API, legacy, and webhook url patterns. We
 | 
			
		||||
will look at how each of these types of requests are handled, and focus
 | 
			
		||||
@@ -140,9 +137,11 @@ yields a response with this HTTP header:
 | 
			
		||||
 | 
			
		||||
We can see this reflected in [zproject/urls.py](https://github.com/zulip/zulip/blob/master/zproject/urls.py):
 | 
			
		||||
 | 
			
		||||
    rest_path('users',
 | 
			
		||||
              GET=get_members_backend,
 | 
			
		||||
              PUT=create_user_backend),
 | 
			
		||||
```python
 | 
			
		||||
rest_path('users',
 | 
			
		||||
          GET=get_members_backend,
 | 
			
		||||
          PUT=create_user_backend),
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In this way, the API is partially self-documenting.
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +174,7 @@ the request, and then figure out which view to show from that.
 | 
			
		||||
 | 
			
		||||
In our example,
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```python
 | 
			
		||||
GET=get_members_backend,
 | 
			
		||||
PUT=create_user_backend
 | 
			
		||||
```
 | 
			
		||||
@@ -195,7 +194,9 @@ This is covered in good detail in the [writing views doc](writing-views.md).
 | 
			
		||||
Our API works on JSON requests and responses. Every API endpoint should
 | 
			
		||||
`raise JsonableError` in the case of an error, which gives a JSON string:
 | 
			
		||||
 | 
			
		||||
`{'result': 'error', 'msg': <some error message>, 'code': 'BAD_REQUEST'}`
 | 
			
		||||
```json
 | 
			
		||||
{"result": "error", "msg": "<some error message>", "code": "BAD_REQUEST"}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
in a
 | 
			
		||||
[HTTP response](https://docs.djangoproject.com/en/1.8/ref/request-response/)
 | 
			
		||||
@@ -203,11 +204,13 @@ with a content type of 'application/json'.
 | 
			
		||||
 | 
			
		||||
To pass back data from the server to the calling client, in the event of
 | 
			
		||||
a successfully handled request, we use
 | 
			
		||||
`json_success(data=<some python object which can be converted to a JSON string>`.
 | 
			
		||||
`json_success(data=<some python object which can be converted to a JSON string>)`.
 | 
			
		||||
 | 
			
		||||
This will result in a JSON string:
 | 
			
		||||
 | 
			
		||||
`{'result': 'success', 'msg': '', 'data'='{'var_name1': 'var_value1', 'var_name2': 'var_value2'...}`
 | 
			
		||||
```json
 | 
			
		||||
{"result": "success", "msg": "", "data": {"var_name1": "var_value1", "var_name2": "var_value2"}}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
with a HTTP 200 status and a content type of 'application/json'.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ organization in Zulip). The following files are involved in the process:
 | 
			
		||||
**Create and run the migration:** To create and apply a migration, run the
 | 
			
		||||
following commands:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```bash
 | 
			
		||||
./manage.py makemigrations
 | 
			
		||||
./manage.py migrate
 | 
			
		||||
```
 | 
			
		||||
@@ -168,14 +168,13 @@ boolean field, `mandatory_topics`, to the Realm model in
 | 
			
		||||
`zerver/models.py`.
 | 
			
		||||
 | 
			
		||||
``` diff
 | 
			
		||||
 # zerver/models.py
 | 
			
		||||
 | 
			
		||||
# zerver/models.py
 | 
			
		||||
 | 
			
		||||
class Realm(models.Model):
 | 
			
		||||
    # ...
 | 
			
		||||
    emails_restricted_to_domains: bool = models.BooleanField(default=True)
 | 
			
		||||
    invite_required: bool = models.BooleanField(default=False)
 | 
			
		||||
+   mandatory_topics: bool = models.BooleanField(default=False)
 | 
			
		||||
 class Realm(models.Model):
 | 
			
		||||
     # ...
 | 
			
		||||
     emails_restricted_to_domains: bool = models.BooleanField(default=True)
 | 
			
		||||
     invite_required: bool = models.BooleanField(default=False)
 | 
			
		||||
+    mandatory_topics: bool = models.BooleanField(default=False)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The Realm model also contains an attribute, `property_types`, which
 | 
			
		||||
@@ -186,18 +185,17 @@ is the field's type. Add the new field to the `property_types`
 | 
			
		||||
dictionary.
 | 
			
		||||
 | 
			
		||||
``` diff
 | 
			
		||||
 # zerver/models.py
 | 
			
		||||
 | 
			
		||||
# zerver/models.py
 | 
			
		||||
 | 
			
		||||
class Realm(models.Model)
 | 
			
		||||
  # ...
 | 
			
		||||
  # Define the types of the various automatically managed properties
 | 
			
		||||
    property_types = dict(
 | 
			
		||||
        add_custom_emoji_policy=int,
 | 
			
		||||
        allow_edit_history=bool,
 | 
			
		||||
        # ...
 | 
			
		||||
+       mandatory_topics=bool,
 | 
			
		||||
        # ...
 | 
			
		||||
 class Realm(models.Model)
 | 
			
		||||
     # ...
 | 
			
		||||
     # Define the types of the various automatically managed properties
 | 
			
		||||
     property_types = dict(
 | 
			
		||||
         add_custom_emoji_policy=int,
 | 
			
		||||
         allow_edit_history=bool,
 | 
			
		||||
         # ...
 | 
			
		||||
+        mandatory_topics=bool,
 | 
			
		||||
         # ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**The majority of realm settings can be included in
 | 
			
		||||
@@ -231,7 +229,7 @@ is helpful.
 | 
			
		||||
Apply the migration using Django's `migrate` command: `./manage.py migrate`.
 | 
			
		||||
 | 
			
		||||
Output:
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
shell $ ./manage.py migrate
 | 
			
		||||
Operations to perform:
 | 
			
		||||
  Synchronize unmigrated apps: staticfiles, analytics, pipeline
 | 
			
		||||
@@ -291,50 +289,54 @@ argument can be a single user (if the setting is a personal one, like
 | 
			
		||||
time display format), members in a particular stream only or all
 | 
			
		||||
active users in a realm.
 | 
			
		||||
 | 
			
		||||
    # zerver/lib/actions.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/lib/actions.py
 | 
			
		||||
 | 
			
		||||
    def do_set_realm_property(
 | 
			
		||||
        realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile]
 | 
			
		||||
    ) -> None:
 | 
			
		||||
      """Takes in a realm object, the name of an attribute to update, the
 | 
			
		||||
         value to update and and the user who initiated the update.
 | 
			
		||||
      """
 | 
			
		||||
      property_type = Realm.property_types[name]
 | 
			
		||||
      assert isinstance(value, property_type), (
 | 
			
		||||
          'Cannot update %s: %s is not an instance of %s' % (
 | 
			
		||||
              name, value, property_type,))
 | 
			
		||||
def do_set_realm_property(
 | 
			
		||||
    realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile]
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Takes in a realm object, the name of an attribute to update, the
 | 
			
		||||
       value to update and and the user who initiated the update.
 | 
			
		||||
    """
 | 
			
		||||
    property_type = Realm.property_types[name]
 | 
			
		||||
    assert isinstance(value, property_type), (
 | 
			
		||||
        'Cannot update %s: %s is not an instance of %s' % (
 | 
			
		||||
            name, value, property_type,))
 | 
			
		||||
 | 
			
		||||
      setattr(realm, name, value)
 | 
			
		||||
      realm.save(update_fields=[name])
 | 
			
		||||
      event = dict(
 | 
			
		||||
          type='realm',
 | 
			
		||||
          op='update',
 | 
			
		||||
          property=name,
 | 
			
		||||
          value=value,
 | 
			
		||||
      )
 | 
			
		||||
      send_event(realm, event, active_user_ids(realm))
 | 
			
		||||
    setattr(realm, name, value)
 | 
			
		||||
    realm.save(update_fields=[name])
 | 
			
		||||
    event = dict(
 | 
			
		||||
        type='realm',
 | 
			
		||||
        op='update',
 | 
			
		||||
        property=name,
 | 
			
		||||
        value=value,
 | 
			
		||||
    )
 | 
			
		||||
    send_event(realm, event, active_user_ids(realm))
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If the new realm property being added does not fit into the
 | 
			
		||||
`property_types` framework (such as the `authentication_methods`
 | 
			
		||||
field), you'll need to create a new function to explicitly update this
 | 
			
		||||
field and send an event. For example:
 | 
			
		||||
 | 
			
		||||
    # zerver/lib/actions.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/lib/actions.py
 | 
			
		||||
 | 
			
		||||
    def do_set_realm_authentication_methods(
 | 
			
		||||
        realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile]
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        for key, value in list(authentication_methods.items()):
 | 
			
		||||
            index = getattr(realm.authentication_methods, key).number
 | 
			
		||||
            realm.authentication_methods.set_bit(index, int(value))
 | 
			
		||||
        realm.save(update_fields=['authentication_methods'])
 | 
			
		||||
        event = dict(
 | 
			
		||||
            type="realm",
 | 
			
		||||
            op="update_dict",
 | 
			
		||||
            property='default',
 | 
			
		||||
            data=dict(authentication_methods=realm.authentication_methods_dict())
 | 
			
		||||
        )
 | 
			
		||||
        send_event(realm, event, active_user_ids(realm))
 | 
			
		||||
def do_set_realm_authentication_methods(
 | 
			
		||||
    realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile]
 | 
			
		||||
) -> None:
 | 
			
		||||
    for key, value in list(authentication_methods.items()):
 | 
			
		||||
        index = getattr(realm.authentication_methods, key).number
 | 
			
		||||
        realm.authentication_methods.set_bit(index, int(value))
 | 
			
		||||
    realm.save(update_fields=['authentication_methods'])
 | 
			
		||||
    event = dict(
 | 
			
		||||
        type="realm",
 | 
			
		||||
        op="update_dict",
 | 
			
		||||
        property='default',
 | 
			
		||||
        data=dict(authentication_methods=realm.authentication_methods_dict())
 | 
			
		||||
    )
 | 
			
		||||
    send_event(realm, event, active_user_ids(realm))
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Update application state
 | 
			
		||||
 | 
			
		||||
@@ -351,27 +353,29 @@ apps). The `apply_event` function in `zerver/lib/events.py` is important for
 | 
			
		||||
making sure the `state` is always correct, even in the event of rare
 | 
			
		||||
race conditions.
 | 
			
		||||
 | 
			
		||||
    # zerver/lib/events.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/lib/events.py
 | 
			
		||||
 | 
			
		||||
    def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
 | 
			
		||||
      # ...
 | 
			
		||||
      if want('realm'):
 | 
			
		||||
def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
 | 
			
		||||
    # ...
 | 
			
		||||
    if want('realm'):
 | 
			
		||||
        for property_name in Realm.property_types:
 | 
			
		||||
            state['realm_' + property_name] = getattr(user_profile.realm, property_name)
 | 
			
		||||
        state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
 | 
			
		||||
        state['realm_allow_message_editing'] = user_profile.realm.allow_message_editing
 | 
			
		||||
        # ...
 | 
			
		||||
 | 
			
		||||
    def apply_event
 | 
			
		||||
        user_profile: UserProfile,
 | 
			
		||||
        # ...
 | 
			
		||||
    ) -> None:
 | 
			
		||||
      for event in events:
 | 
			
		||||
def apply_event
 | 
			
		||||
    user_profile: UserProfile,
 | 
			
		||||
    # ...
 | 
			
		||||
) -> None:
 | 
			
		||||
    for event in events:
 | 
			
		||||
        # ...
 | 
			
		||||
        elif event['type'] == 'realm':
 | 
			
		||||
           field = 'realm_' + event['property']
 | 
			
		||||
           state[field] = event['value']
 | 
			
		||||
           # ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If your new realm property fits the `property_types`
 | 
			
		||||
framework, you don't need to change `fetch_initial_state_data` or
 | 
			
		||||
@@ -380,14 +384,16 @@ property that is handled separately, you will need to explicitly add
 | 
			
		||||
the property to the `state` dictionary in the `fetch_initial_state_data`
 | 
			
		||||
function. E.g., for `authentication_methods`:
 | 
			
		||||
 | 
			
		||||
    # zerver/lib/events.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/lib/events.py
 | 
			
		||||
 | 
			
		||||
    def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
 | 
			
		||||
      # ...
 | 
			
		||||
      if want('realm'):
 | 
			
		||||
          # ...
 | 
			
		||||
          state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
 | 
			
		||||
          # ...
 | 
			
		||||
def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
 | 
			
		||||
    # ...
 | 
			
		||||
    if want('realm'):
 | 
			
		||||
        # ...
 | 
			
		||||
        state['realm_authentication_methods'] = user_profile.realm.authentication_methods_dict()
 | 
			
		||||
        # ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
For this setting, one won't need to change `apply_event` since its
 | 
			
		||||
default code for `realm` event types handles this case correctly, but
 | 
			
		||||
@@ -409,8 +415,7 @@ function in `zerver/views/realm.py` (and add the appropriate mypy type
 | 
			
		||||
annotation).
 | 
			
		||||
 | 
			
		||||
``` diff
 | 
			
		||||
 | 
			
		||||
# zerver/views/realm.py
 | 
			
		||||
 # zerver/views/realm.py
 | 
			
		||||
 | 
			
		||||
 def update_realm(
 | 
			
		||||
     request: HttpRequest,
 | 
			
		||||
@@ -430,15 +435,17 @@ to `zerver/views/realm.py`.
 | 
			
		||||
Text fields or other realm properties that need additional validation
 | 
			
		||||
can be handled at the beginning of `update_realm`.
 | 
			
		||||
 | 
			
		||||
    # zerver/views/realm.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/views/realm.py
 | 
			
		||||
 | 
			
		||||
    # Additional validation/error checking beyond types go here, so
 | 
			
		||||
    # the entire request can succeed or fail atomically.
 | 
			
		||||
    if default_language is not None and default_language not in get_available_language_codes():
 | 
			
		||||
        raise JsonableError(_("Invalid language '%s'" % (default_language,)))
 | 
			
		||||
    if description is not None and len(description) > 100:
 | 
			
		||||
        raise JsonableError(_("Realm description cannot exceed 100 characters."))
 | 
			
		||||
    # ...
 | 
			
		||||
# Additional validation/error checking beyond types go here, so
 | 
			
		||||
# the entire request can succeed or fail atomically.
 | 
			
		||||
if default_language is not None and default_language not in get_available_language_codes():
 | 
			
		||||
    raise JsonableError(_("Invalid language '%s'" % (default_language,)))
 | 
			
		||||
if description is not None and len(description) > 100:
 | 
			
		||||
    raise JsonableError(_("Realm description cannot exceed 100 characters."))
 | 
			
		||||
# ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The code in `update_realm` loops through the `property_types` dictionary
 | 
			
		||||
and calls `do_set_realm_property` on any property to be updated from
 | 
			
		||||
@@ -449,20 +456,22 @@ to call the function you wrote in `actions.py` that updates the database
 | 
			
		||||
with the new value. E.g., for `authentication_methods`, we created
 | 
			
		||||
`do_set_realm_authentication_methods`, which we will call here:
 | 
			
		||||
 | 
			
		||||
    # zerver/views/realm.py
 | 
			
		||||
```python
 | 
			
		||||
# zerver/views/realm.py
 | 
			
		||||
 | 
			
		||||
    # import do_set_realm_authentication_methods from actions.py
 | 
			
		||||
    from zerver.lib.actions import (
 | 
			
		||||
        do_set_realm_message_editing,
 | 
			
		||||
        do_set_realm_authentication_methods,
 | 
			
		||||
        # ...
 | 
			
		||||
    )
 | 
			
		||||
    # ...
 | 
			
		||||
    # ...
 | 
			
		||||
    if authentication_methods is not None and realm.authentication_methods_dict() != authentication_methods:
 | 
			
		||||
            do_set_realm_authentication_methods(realm, authentication_methods, acting_user=user_profile)
 | 
			
		||||
            data['authentication_methods'] = authentication_methods
 | 
			
		||||
# import do_set_realm_authentication_methods from actions.py
 | 
			
		||||
from zerver.lib.actions import (
 | 
			
		||||
    do_set_realm_message_editing,
 | 
			
		||||
    do_set_realm_authentication_methods,
 | 
			
		||||
    # ...
 | 
			
		||||
)
 | 
			
		||||
# ...
 | 
			
		||||
# ...
 | 
			
		||||
if authentication_methods is not None and realm.authentication_methods_dict() != authentication_methods:
 | 
			
		||||
    do_set_realm_authentication_methods(realm, authentication_methods, acting_user=user_profile)
 | 
			
		||||
    data['authentication_methods'] = authentication_methods
 | 
			
		||||
# ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This completes the backend implementation. A great next step is to
 | 
			
		||||
write automated backend tests for your new feature.
 | 
			
		||||
@@ -508,18 +517,17 @@ template.
 | 
			
		||||
Then add the new form control in `static/js/admin.js`.
 | 
			
		||||
 | 
			
		||||
``` diff
 | 
			
		||||
 // static/js/admin.js
 | 
			
		||||
 | 
			
		||||
// static/js/admin.js
 | 
			
		||||
 | 
			
		||||
function _setup_page() {
 | 
			
		||||
    var options = {
 | 
			
		||||
        realm_name: page_params.realm_name,
 | 
			
		||||
        realm_description: page_params.realm_description,
 | 
			
		||||
        realm_emails_restricted_to_domains: page_params.realm_emails_restricted_to_domains,
 | 
			
		||||
        realm_invite_required: page_params.realm_invite_required,
 | 
			
		||||
        // ...
 | 
			
		||||
+       realm_mandatory_topics: page_params.mandatory_topics,
 | 
			
		||||
        // ...
 | 
			
		||||
 function _setup_page() {
 | 
			
		||||
     var options = {
 | 
			
		||||
         realm_name: page_params.realm_name,
 | 
			
		||||
         realm_description: page_params.realm_description,
 | 
			
		||||
         realm_emails_restricted_to_domains: page_params.realm_emails_restricted_to_domains,
 | 
			
		||||
         realm_invite_required: page_params.realm_invite_required,
 | 
			
		||||
         // ...
 | 
			
		||||
+        realm_mandatory_topics: page_params.mandatory_topics,
 | 
			
		||||
         // ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The JavaScript code for organization settings and permissions can be found in
 | 
			
		||||
@@ -582,20 +590,19 @@ setting has changed, your function should be referenced in the
 | 
			
		||||
`settings_emoji.update_custom_emoji_ui`.
 | 
			
		||||
 | 
			
		||||
``` diff
 | 
			
		||||
 // static/js/server_events_dispatch.js
 | 
			
		||||
 | 
			
		||||
// static/js/server_events_dispatch.js
 | 
			
		||||
 | 
			
		||||
function dispatch_normal_event(event) {
 | 
			
		||||
    switch (event.type) {
 | 
			
		||||
    // ...
 | 
			
		||||
    case 'realm':
 | 
			
		||||
      var realm_settings = {
 | 
			
		||||
          add_custom_emoji_policy: settings_emoji.update_custom_emoji_ui,
 | 
			
		||||
          allow_edit_history: noop,
 | 
			
		||||
          // ...
 | 
			
		||||
+         mandatory_topics: noop,
 | 
			
		||||
          // ...
 | 
			
		||||
      };
 | 
			
		||||
 function dispatch_normal_event(event) {
 | 
			
		||||
     switch (event.type) {
 | 
			
		||||
     // ...
 | 
			
		||||
     case 'realm':
 | 
			
		||||
         var realm_settings = {
 | 
			
		||||
             add_custom_emoji_policy: settings_emoji.update_custom_emoji_ui,
 | 
			
		||||
             allow_edit_history: noop,
 | 
			
		||||
             // ...
 | 
			
		||||
+            mandatory_topics: noop,
 | 
			
		||||
             // ...
 | 
			
		||||
         };
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Checkboxes and other common input elements handle the UI updates
 | 
			
		||||
@@ -638,12 +645,14 @@ At the minimum, if you created a new function to update UI in
 | 
			
		||||
`frontend_tests/node_tests/dispatch.js`. Add the name of the UI
 | 
			
		||||
function you created to the following object with `noop` as the value:
 | 
			
		||||
 | 
			
		||||
    # frontend_tests/node_tests/dispatch.js
 | 
			
		||||
```js
 | 
			
		||||
// frontend_tests/node_tests/dispatch.js
 | 
			
		||||
 | 
			
		||||
    set_global('settings_org', {
 | 
			
		||||
        update_email_change_display: noop,
 | 
			
		||||
        update_name_change_display: noop,
 | 
			
		||||
    });
 | 
			
		||||
set_global('settings_org', {
 | 
			
		||||
    update_email_change_display: noop,
 | 
			
		||||
    update_name_change_display: noop,
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Beyond that, you should add any applicable tests that verify the
 | 
			
		||||
behavior of the setting you just created.
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ abbreviation for your home directory (`/home/YOUR_USERNAME` most of the times).
 | 
			
		||||
That's why the following is exactly the same, if the user running it is
 | 
			
		||||
`john`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ cd ~
 | 
			
		||||
$ cd /home/john
 | 
			
		||||
```
 | 
			
		||||
@@ -58,7 +58,7 @@ directory, instead of writing the whole path.
 | 
			
		||||
Imagine you have a file called `ideas.txt` inside `/home/john/notes/`, and
 | 
			
		||||
you want to edit it using `nano`. You could use:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ nano /home/john/notes/ideas.txt
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -69,7 +69,7 @@ That's why it's very useful to change the path where you are currently
 | 
			
		||||
located (usually known as **working directory**). To do that, you use `cd`
 | 
			
		||||
(**c**hange **d**irectory):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ cd /home/john/notes/
 | 
			
		||||
~/notes$ nano ideas.txt
 | 
			
		||||
```
 | 
			
		||||
@@ -99,7 +99,7 @@ In case you were wondering, the name `sudo` comes from **s**uper **u**ser
 | 
			
		||||
Some characters cannot be used directly in the shell, because they have a
 | 
			
		||||
special meaning. Consider the following example:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ echo "He said hello"
 | 
			
		||||
He said hello
 | 
			
		||||
```
 | 
			
		||||
@@ -118,7 +118,7 @@ before it.
 | 
			
		||||
 | 
			
		||||
Returning to our example:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ echo "He said \"hello\""
 | 
			
		||||
He said "hello"
 | 
			
		||||
```
 | 
			
		||||
@@ -138,7 +138,7 @@ the shell provides two different separators:
 | 
			
		||||
- **Semicolon `;`**: runs a command, and once it has finished, runs the next
 | 
			
		||||
  one:
 | 
			
		||||
 | 
			
		||||
  ```
 | 
			
		||||
  ```console
 | 
			
		||||
  $ echo "Hello"; echo "World!"
 | 
			
		||||
  Hello
 | 
			
		||||
  World!
 | 
			
		||||
@@ -147,7 +147,7 @@ the shell provides two different separators:
 | 
			
		||||
- **Double ampersand `&&`**: runs a command, and **only if** it finished
 | 
			
		||||
  without errors, it proceeds with the next one:
 | 
			
		||||
 | 
			
		||||
  ```
 | 
			
		||||
  ```console
 | 
			
		||||
  $ qwfvijwe && echo "Hello"
 | 
			
		||||
  qwfvijwe: command not found
 | 
			
		||||
  ```
 | 
			
		||||
@@ -158,7 +158,7 @@ the shell provides two different separators:
 | 
			
		||||
  When using an incorrect command with a semicolon, the `Hello` will still
 | 
			
		||||
  be printed:
 | 
			
		||||
 | 
			
		||||
  ```
 | 
			
		||||
  ```console
 | 
			
		||||
  $ qwfvijwe; echo "Hello"
 | 
			
		||||
  qwfvijwe: command not found
 | 
			
		||||
  Hello
 | 
			
		||||
@@ -176,7 +176,7 @@ shell "wait, there's more on the next line".
 | 
			
		||||
This is an example, taken from the docs on how to install the Zulip development
 | 
			
		||||
environment:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```bash
 | 
			
		||||
sudo apt-get -y purge vagrant && \
 | 
			
		||||
curl -fLO https://releases.hashicorp.com/vagrant/2.0.2/vagrant_2.0.2_x86_64.deb && \
 | 
			
		||||
sudo dpkg -i vagrant*.deb && \
 | 
			
		||||
@@ -204,7 +204,7 @@ Most commands need additional data to work, like a path or a file. That extra
 | 
			
		||||
information is called an **argument**, and it's specified after the name of the
 | 
			
		||||
command, like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ cd /home/john/notes
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -221,7 +221,7 @@ different meanings.
 | 
			
		||||
Sometimes, a command can accept arguments indicated with dashes. Here's another
 | 
			
		||||
example of arguments usage:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ nano -C /home/john/backups --mouse todo.txt
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -245,7 +245,7 @@ Note that the `todo.txt` is the file we want to open! It has nothing to do with
 | 
			
		||||
the previous argument. This will probably clarify it (taken from `nano`'s
 | 
			
		||||
help):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
Usage: nano [OPTIONS] [FILE]...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -263,13 +263,13 @@ them.
 | 
			
		||||
That's why you may have seen cases, in the Zulip codebase or
 | 
			
		||||
elsewhere, when some Python scripts are called with `python`:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ python my_program.py
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
While other times, `python` isn't used:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ ./my_program.py
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -281,7 +281,7 @@ The note telling the OS how to interpret the file goes on the file's
 | 
			
		||||
very first line, and it's called a **shebang**. In our Python scripts,
 | 
			
		||||
it looks like this:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```python
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -294,7 +294,7 @@ added as a command-line argument. So, returning to our example with
 | 
			
		||||
`my_program.py`, when you run `./my_program.py`, what happens under
 | 
			
		||||
the hood is equivalent to:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
```console
 | 
			
		||||
$ /usr/bin/env python3 ./my_program.py
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user