2. The Basics

Now that we’ve covered the boring stuff, like compiling and executing your CoffeeScript, we will start covering how to actually write it. Let’s dive right in.

In this chapter we are going to examine the syntax of CoffeeScript. We’ll look at punctuation, scope, variables, and a few other choice bits.

Syntax

Much of CoffeeScript’s press has been due to its syntax, in particular its lack of punctuation. Punctuation such as curly braces and semicolons are extinct in the world of CoffeeScript, and parentheses are an endangered species.

To illustrate this point, let’s take a look at a bit of JavaScript that most of you might be familiar with. Here is a piece of jQuery code to make a remote AJAX request and do some work with the results:

Example: (source: jquery_example.js)


$(function() {
  $.get('example.php', function(data) {
    if (data.errors != null) {
      alert("There was an error!");
    } else {
      $("#content").text(data.message);
    }
  }, 'json')
})


CoffeeScript allows us to strip out a lot of the extra punctuation in that example. Here is what the same code written in CoffeeScript would look like:

Example: (source: jquery_as_coffee.coffee)


$ ->
  $.get 'example.php', (data) ->
      if data.errors?
        alert "There was an error!"
      else
        $("#content").text data.message
    , 'json'


Later in this book we are going to get into greater detail on what most of the parts of that example are doing, but for now let’s examine what we took out of the JavaScript example when writing our CoffeeScript example.

Significant Whitespace

The first thing we did was to remove all curly braces and semicolons.

Example: (source: jquery.js)


$(function()
  $.get('example.php', function(data)
    if (data.errors != null)
      alert("There was an error!")
    else
      $("#content").text(data.message)
  , 'json')
)


How does this work? How does CoffeeScript know how to parse the code to make sense of it? The answer is quite simple, and chances are it is something you are already doing every day: whitespace! CoffeeScript, like Python, uses the concept of significant whitespace to tell it how to parse expressions.

I’ve heard people grumble about significant whitespace before saying they don’t like it. I find that to be an unusual argument. Would you write the same JavaScript example like the following?

Example: (source: jquery.js)


$(function() {
$.get('example.php', function(data) {
if (data.errors != null) {
alert("There was an error!");
} else {
$("#content").text(data.message);
}
}, 'json')
})


I should hope not! If you are writing JavaScript this way, I beg you to do your fellow developers a favor and take the extra second to make sure your code is properly indented. Readability is key to maintenance. It is also the key to helping you convert your existing JavaScript to CoffeeScript.

With significant whitespace, CoffeeScript knows that when you indent a line below your if statement the compiler should position that line inside of the if block. The next time the compiler sees indentation at the same level as that if statement, it knows that you are finished writing your if statement and executes that line at the same level as the if statement.

Here is a brief example of the type of error you would get if you did not properly format your CoffeeScript code:

Example: (source: whitespace.coffee)


for num in [1..3]
    if num is 1
      console.log num
       console.log num * 2
  if num is 2
        console.log num
        console.log num * 2


Output: (source: whitespace.coffee)


Error: In content/the_basics/whitespace.coffee, Parse error on line 4: Unexpected 'INDENT'
    at Object.parseError (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/parser.js:470:11)
    at Object.parse (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/parser.js:546:22)
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:40:22
    at Object.run (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:68:34)
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:135:29
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:110:18
    at [object Object].<anonymous> (fs.js:114:5)
    at [object Object].emit (events.js:64:17)
    at afterRead (fs.js:1081:12)
    at Object.wrapper [as oncomplete] (fs.js:252:17)


Function Keyword

The next thing we did was eliminate that old function keyword. None of the CoffeeScript code we will write will ever use the function keyword.

Example: (source: jquery.js)


$( ()->
  $.get('example.php', (data)->
    if (data.errors != null)
      alert("There was an error!")
    else
      $("#content").text(data.message)
  , 'json')
)


Instead of the function keyword we can use an arrow, ->, to the right of the arguments list. This is a little difficult to remember and get straight at first, I know, but it actually makes a lot of sense if you think about the flow of the code and where the arguments are “pointing.”

Parentheses

Next we removed some, but not all, of our parentheses in the example code.

Example: (source: jquery.js)


$ ->
  $.get 'example.php', (data)->
    if data.errors != null
      alert "There was an error!"
    else
      $("#content").text data.message
  , 'json'


Why didn’t we remove all parentheses? In almost all cases, removing parentheses is optional. The rules about when to use the parentheses can be a bit confusing, especially when we talk about functions. Let’s take a look at our code to see why we left some in and took some out.

When we went to call the alert function, like this:

alert "There was an error!"

we were able to remove the parentheses. The reason is that if a function is being passed arguments, we can omit the parentheses. However, if a function is not being passed any arguments, we need to supply the parentheses so that JavaScript knows that we are calling a function and not a variable. Like I said: a bit confusing.


Tip

When in doubt, you can use the parentheses all the time if you feel it helps to make your code cleaner and easier to read.


So, if we don’t need the parentheses when calling a function with arguments, why did we use parentheses on this line?

$("#content").text data.message

Why didn’t we write it like this instead?

$ "#content" .text data.message

If we had done that, the compiled JavaScript would look like this for that line:

$("#content".text(data.message));

As you can see, CoffeeScript isn’t sure what you are calling the text function on, so it assumes it is the string "#content". By leaving the parentheses in there, we are telling CoffeeScript where exactly it needs to call the text method; in this case it’s on the jQuery object returned by $("#content").

Before we move away from the subject of parentheses (don’t worry we’ll be talking about them again when we talk about functions), I want to point out that parentheses can still be used for logical grouping.

Example: (source: grouping.coffee)


if x is true and (y is true or z is true)
  console.log 'hello, world'


Example: (source: grouping.js)


(function() {

  if (x === true && (y === true || z === true)) console.log('hello, world'),

}).call(this);


Scope and Variables

In this section we talk about how scope and variables work and are defined in CoffeeScript. In JavaScript this can be a tricky subject, often the source of confusion and bugs. In this section we see how CoffeeScript tries to make bugs related to scope a thing of the past.

Variable Scope in JavaScript

When declaring variables in JavaScript, a lot of people, both experienced and beginner, do not realize that there are two ways to declare variables. If they do know the two ways to declare variables, they may not know the difference between the two. Because of that, let’s take a brief second to look at the two ways and understand, at least on a basic level, what they do.

Look at the following code snippet:

Example: (source: js_variable_scope.js)


a = 'A';
myFunc = function() {
  a = 'AAA';
  var b = 'B';
}


If we were to run that code in a browser or another JavaScript engine, we would get the following:

Output:


> console.log(a)
A
> myFunc();
> console.log(a)
AAA
> console.log(b)
ReferenceError: b is not defined


Chances are you probably were expecting that when trying to reference variable b it would raise an error. However, you were probably not expecting variable a to return the value defined in the myFunc function, were you? So why is that?

The answer is simple and goes to the heart of the difference between the way those two variables were defined.

When we defined variable a without using the var keyword, we were telling JavaScript to create that variable in the global namespace. Because a variable called a already existed in the global namespace, we clobbered our original variable and replaced it with the new one we defined inside of myFunc. Oops.

The variable b was defined inside of the myFunc function using the keyword var, which told JavaScript to create a variable named b and scope it to inside the function myFunc. Because the variable b is scoped inside the function when we tried to access the variable outside of the function, we got an error because JavaScript couldn’t find a variable named b defined in the global namespace.


Tip

The example shown in the section “Variable Scope in JavaScript” demonstrates why you should always use the keyword var when defining variables. This is definitely a best practice, and CoffeeScript wants to help make sure you always do that.


Variable Scope in CoffeeScript

Now that we understand a little something about how variable scoping works in JavaScript, let’s take a quick look at our example again, this time written in CoffeeScript:

Example: (source: coffeescript_variable_scope.coffee)


a = 'A'
myFunc = ->
  a = 'AAA'
  b = 'B'


Example: (source: coffeescript_variable_scope.js)


(function() {
  var a, myFunc;

  a = 'A';

  myFunc = function() {
    var b;
    a = 'AAA';
    return b = 'B';
  };

}).call(this);


Ignoring for a moment the anonymous wrapper function around our code (we will be talking about that shortly), let’s look at the way CoffeeScript has declared our variables. Notice that each of our variables, including the variable pointing to our myFunc function, is declared with the var keyword. CoffeeScript has our back to ensure sure we do the right thing in terms of variable declaration.

One rather interesting thing to point out about this code is that although CoffeeScript helps us with proper variable declaration, it still doesn’t prevent us from clobbering our original a variable. The reason for this is that when CoffeeScript is compiling the JavaScript, it sees that there was a previously defined variable named a and assumes that you intended to use that variable inside of your function.

The Anonymous Wrapper Function

As you’ve seen, and should have noticed, all of our compiled JavaScript has been wrapped in an anonymous, self-executing function. I know by now that you are wondering what that function is doing there. Let me tell you.

As we saw with our JavaScript variable scope examples, we were able to easily access variables that were defined without the var keyword. When we defined those variables, they ended up in a global namespace that is easily accessible by everyone.

When we defined our a variable, even using the var keyword, we were still defining it in the global namespace. Why is that, you ask? The reason is quite simple—we defined it outside of any functions. Because the variable was defined outside of a function scope, it is available to the global namespace. That means that if another library you are using also defines a, then one of the two variables will be clobbered by the one that was last to be defined. This, of course, is true of any namespace, not just the global one.

So how do you define variables, and functions, outside of the global namespace so they are accessible to your program and no one else’s? You do that by wrapping your code in an anonymous wrapper function. This is exactly what CoffeeScript is doing for you when it compiles your code to JavaScript.

Now, this is the point where you should be thinking two things. The first is, “That’s clever, I can do whatever I want in there and not have to worry about polluting the global namespace.” The second thing you should be asking yourself is, “Wait, how do I expose the variables and functions I want to the global namespace so I and others can access them?” Those are two very important thoughts. Let’s address the second, because it’s the one that needs to be addressed.

If you were to write your entire program or library within the confines of an anonymous function, as CoffeeScript forces you to do, no other libraries or code in your application could access your code. That might be just what you want to happen. However, if it is not what you want, there are a few ways we can remedy this problem.

Here’s an example of one way we could share a function with the outside world:

Example: Exposing with window (source: expose_with_window.coffee)


window.sayHi = ->
  console.log "Hello, World!"


Example: Exposing with window (source: expose_with_window.js)


(function() {

  window.sayHi = function() {
    return console.log("Hello, World!");
  };

}).call(this);


In that example we are using the window object to expose our function. In a world where all our code is being executed in the browser, this is a perfectly good way to expose the function. However, with the success of Node.JS and other server-side JavaScript technologies, it is becoming more and more popular to run JavaScript in environments other than that of the browser. If we were to try to run this using the coffee command, we would get the following output:

Example: Exposing with window (source: expose_with_window.coffee.output)


ReferenceError: window is not defined
    at Object.<anonymous> (.../the_basics/expose_with_window.coffee:3:3)
    at Object.<anonymous> (.../the_basics/expose_with_window.coffee:7:4)
    at Module._compile (module.js:432:26)
    at Object.run (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:68:25)
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:135:29
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script/command.js:110:18
    at [object Object].<anonymous> (fs.js:114:5)
    at [object Object].emit (events.js:64:17)
    at afterRead (fs.js:1081:12)
    at Object.wrapper [as oncomplete] (fs.js:252:17)


When running the coffee command, there is no window object to expose our function through. So how do we solve this problem? The answer is very simple. When CoffeeScript creates the anonymous function wrapper for us, it conveniently passes in this. We can easily attach our function to this and expose it that way. Let’s look:

Example: Exposing with this (source: expose_with_this.coffee)


this.sayHi = ->
  console.log "Hello, World!"


Example: Exposing with this (source: expose_with_this.js)


(function() {

  this.sayHi = function() {
    return console.log("Hello, World!");
  };

}).call(this);


If we were to execute that code in a browser, our function would be exposed to other JavaScript code because this in the browser refers to the window object. In the coffee command or in a Node.JS application this refers to the global object. By using this to expose your functions and variables, you make your code both future and platform proof.

Now for the sake of completeness, there is an even simpler and, I think, cleaner way of exposing your code, and that is like this:

Example: Exposing with @ (source: expose_with_at.coffee)


@sayHi = ->
  console.log "Hello, World!"


Example: Exposing with @ (source: expose_with_at.js)


(function() {

  this.sayHi = function() {
    return console.log("Hello, World!");
  };

}).call(this);


As you can see in the compiled JavaScript, the @ symbol in CoffeeScript got compiled out to this.. In CoffeeScript, wherever you would use this. you can replace it with @ and expect it to work the same way. Although using @ is optional, it is the preferred way in CoffeeScript, so I will be using it exclusively throughout the book.

Interpolation

In this section we talk about string interpolation, heredocs, and comments in CoffeeScript. String interpolation will let us easily build dynamic strings without having to worry about annoying and error-prone concatenation syntax. Heredocs allow us to easily build nicely formatted multiline strings. Comments, well, should be self-explanatory.

String Interpolation

One of my personal pet peeves in JavaScript is trying to build a dynamic string. Let me give you an example. Let’s build an HTML text field in JavaScript that is using a few dynamic attributes:

Example: (source: javascript_concatenation.js)


var field, someId, someName, someValue;
someName = 'user[firstName]';
someId = 'firstName';
someValue = 'Bob Example';
field = "<input type='text' name='" + someName + "' id='" + someId + "' value='" + (escape(someValue)) + "'>";
console.log(field);


Output: (source: javascript_concatenation.js)


<input type='text' name='user[firstName]' id='firstName' value='Bob%20Example'>


See how ugly, confusing, and potentially buggy that code is? Did I remember to properly close all the ' around my tag attributes? Did I add the correct number of "? I think so, but it certainly isn’t easy to see at a glance.

CoffeeScript has followed the lead of some of the more modern languages, like Ruby, and gives us two different types of strings, interpolated and literal. Let’s look at them.

Interpolated Strings

To get rid of all the nasty concatenation we saw in our HTML text field example, CoffeeScript lets us, instead, use string interpolation to solve the problem.

What is string interpolation? String interpolation is a way for us to inject arbitrary CoffeeScript code inside of a string and have it executed at runtime. In our example, we want to stick a few variables into our HTML string, and CoffeeScript lets us do that. Here’s how you could write the same example, this time using CoffeeScript’s string interpolation:

Example: (source: html_string_interpolation.coffee)


someName = 'user[firstName]'
someId = 'firstName'
someValue = 'Bob Example'

field = "<input type='text' name='#{someName}' id='#{someId}' value='#{escape someValue}'>"

console.log field


Example: (source: html_string_interpolation.js)


(function() {
  var field, someId, someName, someValue;

  someName = 'user[firstName]';

  someId = 'firstName';

  someValue = 'Bob Example';

  field = "<input type='text' name='" + someName + "' id='" + someId + "' value='" + (escape(someValue)) + "'>";

  console.log(field);

}).call(this);


Output: (source: html_string_interpolation.coffee)


<input type='text' name='user[firstName]' id='firstName' value='Bob%20Example'>


Doesn’t that code look better? That code is easier to read, write, and maintain.

In JavaScript, as you know, there is no such thing as interpolated strings. All strings are considered equal. In CoffeeScript, all strings are not created equal. Double-quoted strings, such as the one we just used, tell the CoffeeScript compiler to process the string and turn it into a concatenated JavaScript string, if necessary. Single-quoted strings are called literal strings in CoffeeScript, and we’ll look at them in just a minute.

When we want to inject some CoffeeScript into a double-quoted string, we use the #{} syntax. Everything between the two curly braces will be separated out by the compiler and then concatenated to the string we are building. The code we put inside the curly braces can be any valid CoffeeScript we would like:

Example: (source: string_interpolation_extra.coffee)


text = "Add numbers: #{1 + 1}"
console.log text

text = "Call a function: #{escape "Hello, World!"}"
console.log text

day = 'Sunday'
console.log  "It's a beautiful #{if day is 'Sunday' then day else "Day"}"


Example: (source: string_interpolation_extra.js)


(function() {
  var day, text;

  text = "Add numbers: " + (1 + 1);

  console.log(text);

  text = "Call a function: " + (escape("Hello, World!"));

  console.log(text);

  day = 'Sunday';

  console.log("It's a beautiful " + (day === 'Sunday' ? day : "Day"));

}).call(this);


Output: (source: string_interpolation_extra.coffee)


Add numbers: 2
Call a function: Hello%2C%20World%21
It's a beautiful Sunday


Literal Strings

Literal strings are just what their name suggests, literal strings. That means that whatever you put into the string is exactly what you get back from the string; this is how JavaScript behaves.

To build a literal string in CoffeeScript you need to use single quotes, '. Let’s revisit our previous example of building an HTML text field. This time, instead of using double quotes around our string, let’s use single quotes and see what happens:

Example: (source: html_string_literal.coffee)


someName = 'user[firstName]'
someId = 'firstName'
someValue = 'Bob Example'

field = '<input type='text' name='#{someName}' id='#{someId}' value='#{escape(someValue)}'>'

console.log field


Example: (source: html_string_literal.js)


(function() {
  var field, someId, someName, someValue;

  someName = 'user[firstName]';

  someId = 'firstName';

  someValue = 'Bob Example';

  field = '<input type='text' name='#{someName}' id='#{someId}' value='#{escape(someValue)}'>';

  console.log(field);

}).call(this);


Output: (source: html_string_literal.coffee)


<input type='text' name='#{someName}' id='#{someId}' value='#{escape(someValue)}'>


As we can tell by our output, we are not getting the desired outcome. This is because literal strings do not support string interpolation. Instead of seeing our dynamic content mixed into the string, we are just seeing the placeholders for that dynamic content. There are certainly times when this would be the desired outcome; however, those times are few and far between.

Although literal strings won’t let you inject any dynamic content into them, they still do a little bit of parsing and manipulation, the same as JavaScript. Literal strings in CoffeeScript still let you use common escape characters. Take, for example, the following:

Example: (source: literal_string_with_escapes.coffee)


text = "Header Indented Text"
console.log text


Example: (source: literal_string_with_escapes.js)


(function() {
  var text;

  text = "Header Indented Text";

  console.log(text);

}).call(this);


Output: (source: literal_string_with_escapes.coffee)


Header
         Indented Text


As you can see, our newline character, , and our tab character, , were properly interrupted and handled correctly in the output. CoffeeScript, like JavaScript, allows us to use double backslashes to escape a single backslash, as seen here:

Example: (source: literal_string_with_backslash.coffee)


text = "Insert \some\ slashes!"
console.log text


Example: (source: literal_string_with_backslash.js)


(function() {
  var text;

  text = "Insert \some\ slashes!";

  console.log(text);

}).call(this);


Output: (source: literal_string_with_backslash.coffee)


Insert some slashes!


In languages like Ruby a performance improvement can be had by using literal strings. The runtime environment doesn’t need to parse the string and do the required manipulation on it as the program is executing. However, because CoffeeScript compiles down to JavaScript, the performance gain moves from runtime to compilation time.


Tip

The performance gain found by using literal strings instead of interpolated strings is found only at compilation time. I see no downside to using double-quoted, interpolated strings all the time. Even if you are not using the power of interpolated strings right now, you make it easy to use at a later date by making all your strings interpolated.


Heredocs

A heredoc1, or here document, lets you build a multiline string easily in CoffeeScript, while preserving all the spaces and newlines of the multiline string. Heredoc strings follow the same rules as interpolated and literal strings do. To build an interpolated heredoc string in CoffeeScript, you use three double quotes at each end of the string. To build a literal heredoc string, you would use three single quotes at each end of the string.

Let’s look at a simple example. Let’s take our previous HTML text field and add some more HTML around it:

Example: (source: heredoc.coffee)


someName = 'user[firstName]'
someId = 'firstName'
someValue = 'Bob Example'

field = """
        <ul>
          <li>
            <input type='text' name='#{someName}' id='#{someId}' value='#{escape(someValue)}'>
          </li>
        </ul>
        """

console.log field


Example: (source: heredoc.js)


(function() {
  var field, someId, someName, someValue;

  someName = 'user[firstName]';

  someId = 'firstName';

  someValue = 'Bob Example';

  field = "<ul>   <li>     <input type='text' name='" + someName + "' id='" + someId + "' value='" + (escape(someValue)) + "'>   </li> </ul>";

  console.log(field);

}).call(this);


Output: (source: heredoc.coffee)


<ul>
  <li>
    <input type='text' name='user[firstName]' id='firstName' value='Bob%20Example'>
  </li>
</ul>


As you can see, our final output was nicely formatted, just like our original text. You can also see that the original indentation level the heredoc begins with is maintained throughout, making it easy to keep the code well formatted.

Comments

Every good language needs to supply more than one way to add comments, and CoffeeScript is no different. There are two ways to write comments in CoffeeScript, and both ways have different effects on the compiled JavaScript.

Inline Comments

The first type of comment is the inline comment. Inline comments are very simple. To create an inline comment, you simply use a # symbol. Everything after the # symbol to the end of the line will be ignored by the CoffeeScript compiler.

Example: (source: inline_comment.coffee)


# Calculate the company payroll
calcPayroll()

payBils() # Pay the company's bills


Example: (source: inline_comment.js)


(function() {

  calcPayroll();

  payBils();

}).call(this);


You can see that our comments do not make their way into the final JavaScript source. There is some debate as to whether this is a good thing or a bad thing. It would be nice to have the comment next to the JavaScript code. However, because there is not always a nice, direct mapping from CoffeeScript to JavaScript, it makes it difficult for the compiler to always know where the comment should go. On the plus side, our compiled JavaScript is lighter because it is not peppered with comments, leaving us to comment our code fully without fear of bloating the JavaScript.

Block Comments

The other type of comment that CoffeeScript gives us is a block comment. Block comments are great for writing lengthy, multiline comments. Such comments can include license and versioning information, documentation of API usage, and so on. Unlike inline comments, CoffeeScript does include block comments in the compiled JavaScript code.

Example: (source: block_comment.coffee)


###
My Awesome Library v1.0
Copyright: Me!
Released under the MIT License
###


Example: (source: block_comment.js)


/*
My Awesome Library v1.0
Copyright: Me!
Released under the MIT License
*/

(function() {



}).call(this);


Defining block comments, as you can see, is very similar to defining heredocs. Block comments are defined by using three # symbols on both sides of the comment.

Extended Regular Expressions

CoffeeScript and JavaScript are identical in how you define, use, and execute regular expressions.2 CoffeeScript, however, does give a little help when you want to write those really long and complex regular expressions.

We’ve all had that regular expression that gets a bit unwieldy. We would love to split it up over several lines and comment each of the sections of the expression. Well, CoffeeScript is there to help us out.

To define a multiline regular expression, we wrap the expression with three forward slashes on either side of the expression, similar to heredocs and block comments.

Let’s look at an actual usage of this from the CoffeeScript compiler source code:

Example: (source: extended_regex.coffee)


REGEX = /// ^
  (/ (?! [s=] )   # disallow leading whitespace or equals signs
  [^ [ / \ ]*  # every other thing
  (?:
    (?: \[sS]   # anything escaped
      | [         # character class
           [^ ] \ ]*
           (?: \[sS] [^ ] \ ]* )*
         ]
    ) [^ [ / \ ]*
  )*
  /) ([imgy]{0,4}) (?!w)
///


Example: (source: extended_regex.js)


(function() {
  var REGEX;

  REGEX = /^(/(?![s=])[^[/ \]*(?:(?:\[sS]|[[^] \]*(?:\[sS] [^] \]*)*])[^[/ \]*)*/)([imgy]{0,4})(?!w)/;

}).call(this);


You can see how all the extra whitespace and the comments are removed from our JavaScript output.

Wrapping Up

Now that you understand the syntax of CoffeeScript, we can begin looking at more interesting and detailed parts of the language. If you don’t understand anything we’ve covered so far, please take a few minutes to go back and reread this chapter again. It’s important that you understand all that we have discussed here because it forms the basis for everything you will be learning throughout the rest of the book. When you’re comfortable with everything, let’s move on to the next chapter!

Notes

1. http://en.wikipedia.org/wiki/Heredoc

2. http://en.wikipedia.org/wiki/Regular_expressions

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.176.145