вторник, 11 июня 2013 г.

CoffeeScript

Установка coffescript

npm install -g coffee-script

Вывод подсказки по интерпретатору coffeescript

coffee -h

Вывод версии coffeescript

coffee -v

Запуск интерпретатора coffescript

coffee

Выход из интерпретатора coffescript

Ctrl + C
Ctrl + C

Запуск файла coffeescript в интерпретаторе

coffee my/folder/myfile.coffee

Компиляция файла coffescript

coffee -c myfile.coffee

Компиляция файла в целевую папку (Компиляция всегда создает файл с тем же именем, что и оригинальный файл)

coffee -o /to/my/javascript/folder -c /from/my/folder/myfile.coffee

Убрать при компиляции подстановку кода в анонимную функцию обертку (function(){...}).call(this)

coffee -b -c myfile.coffee

Вывод результата компиляции на экран

coffee -p myfile.coffee

Автоматическая компиляция файлов coffeescript при их обновлении в локальной папке

coffee -w -c /to/my/javascript/folder

Для написания coffeescript-кода из javascript-кода:
1) Удаляем все фигурные скобки ({) (}) и точки с запятыми (;).
2) Расставляем правильно пробелы для обеспечения отступов в коде.
3) Заменяем везде слово function на символ (->) после круглых скобок.
4) Хотя круглые скобки удалять необязательно. При наличии сомнений всегда пишите круглые скобки.
    Круглые скобки можно использовать для группировки кода.
    Удаляем круглые скобки (), но не все.
    Оставляем круглые скобки в местах, где при определении функции перечислены подставляемые переменные.
    При вызове функции и передаче ей аргументов скобки можно не писать.
5) при объявлении переменных не используем слово var.

Экспортирование функции в глобальную зону видимости глобального объекта: window, this или @

window.sayHi -> console.log('Hello, World!')

this.sayHi -> console.log('Hello, World!')

@sayHi -> console.log('Hello, World!')

Результат

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

Значение this можно обозначать как @.

Вставка значения внутрь строки #{...}, расположенной в двойных кавычках " ". Строки в одинарных кавычках ' ' вставку значений не поддерживают.

someName = "Boris"
files = "<My name is #{someName}"

Внутри #{} иожет быть любой coffeescript-код

text = "Add numbers: #{1+1}"
console.log "It's beautiful #{if day is 'Sunday' then day else 'Day'}"

Для задания отформатированного многострочного текста используются тройные кавычки """ ... """ или ''' ... '''

text = """
         This
         is
         my
         text
         """

console.log text

Комментарии

# это однострочный комментарий coffeescript. Он не попадет в код javascript.

###
Это
многострочный
комментарий
coffeescript.
Он попадет в код javascript.
###

В JavaScript-коде появится такой комментарий.

/*
Это
многострочный
комментарий
coffeescript.
Он попадет в код javascript.
*/

Регулярные выражения в coffeescript пишутся так же как и в javascript.

Расширенная запись регулярного выражения в coffeescript. Выражение внутри /// ... /// можно записывать в несколько строк и комментировать.

pattern = /// ^ # слово начинается с
                     (a|b) # символа a или b
                            {3} # повторенного 3 раза подряд
                                 $ # и окончивается им
///

Компилятор coffeescript автоматически преобразует == в ===
Поэтому используйте в coffeescript только == и !=

Проверка существования переменной ?

console.log x?
console.log someObject?.someFunction()

Результат

(function(){
  console.log(typeof x !== "undefined" && x !== null);
  console.log(typeof someObject !== "undefined" && someObject !== null ? someObject.someFunction() : void 0);
}).call(this)

Псевдонимы операторов
Внимание! Лучше использовать псевдонимы CoffeScript!

CoffeeScript | JavaScript
                  |
is                |  === 
isnt             |  !==
not              |  !
and              | &&
or                |  ||
true, yes, on |  true
false, no, off |  false
@, this         |  this
of                |  in
in                |  не определен

Пример

name = "Mark"
console.log name is "Mark"
console.log name isnt "Bob"

Внимание! Нельзя использовать конструцию is not. is not не равно isnt! Это может привести к логической ошибке!

Условные выражения

today = "Monday"
if today is "Sunday"
    console.log "Today is Sunday"
else
    console.log "Today is #{today}"

Однострочное условное выржение
Однострочные вырожения сложнее для восприятия

today = "Monday"
console.log if today is "Sunday" then "Today is Sunday" else "Today is #{today}"

CoffeeScript не поддерживает выражения вида a === b ? c = 1 : c = 2

If - Else If - Else

today = "Monday"
if today is "Sunday"
    console.log "Today is Sunday"
else if today is "Saturday"
    console.log "Today is Saturday"
else
    console.log "Today is #{today}"

Unless - противоположность If. Если выражение равно false (взамен true у if()), то выполнить блок кода.

today = "Monday"

unless today is "Sunday"
    console.log "No football today!"

Результат

(function(){
    var today = "Monday";
    if (today !== "Sunday") {console.log("No footbal today!");}
}).call(this);

Условные инструкции

today = "Sunday"

console.log "Today is Sunday" if today is "Sunday"

Конструкция Switch - Case - Break - Default в coffeescript заменяется на Switch - When - , - Else

today = "Monday"

switch today
    when "Saturday"
        console.log "It is Saturday"
    when "Sunday", "Monday"
        console.log "It is Sunday"
    else
        console.log "Let's go to work!"

Создание функций в coffeescript

myFunction = () ->
    console.log "Hello!"

или можно записать так

myFunction = ->
    console.log "Hello!"

или так

myFunction = -> console.log "Hello!" (напоминает EcmaScript 6: var a = x => x * x)

myFunction()

Но мне кажется, что скобки лучше писать!

CoffeScript при компиляции автоматически добавляет в каждую функцию return, возвращая последнее значение.
Если вам нажо возвращать другое значение, то добавьте return в вашу функции в coffescript.

myFunction = () ->
    x = 1
    y = 2
    return x

Если не нужно, чтобы функция возвращала какое-либо значение, то доабавьте в концее её return null или return undefined

myFunction = () ->
    x = 1
    y = 2
    return undefined

Определение функции с аргументами

myFunction = (name, surname) ->
    console.log "My nam is #{name} #{surname}"

myFunction("Boris", "Viktorov")

или можно записать вызов функции так

myFunction "Boris", "Viktorov"

Но лучше использовать скобки!

При определении функции можно задавать значения по умолчанию

myFunction = (name = "Stanislav", surname = "Nikolaev") ->
    console.log "My nam is #{name} #{surname}"

myFunction()

В качестве значений по умолчанию можно использовать и функции

defaultRate = () ->
    return 0.05

calculateTotal = (num, rate = defaultrate()) ->
    return num * rate

Важно! Значения по умолчанию должны идти последними в списке аргументов функции, так как они являются по смыслу необязательными.

Переменное число рагусментов.
Групповые аргументы обозначаются троеточием (...) после названия группового аргумента.
Троеточие  (...) переводится как "и другие..."
Групповые аргументы args[] и kwargs{} используются когда функция может по смыслу принимать переменное число аргументов.
Групповые аргументы, в отличии от аргументов по умолчанию, можно помещать в любое место перечня аргументов функции. Но одна функция может иметь только один список групповых аргументов!

myFunction = (etc...) ->
    console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"

myFunction("a", "b", "c")

Результат

(function(){
    var myFunction = function(){
        var etc;
        etc = 1 <= arguments.length ? Array.protorype.slice.call(arguments, 0) : [];
        console.log("Length: " + etc.length + ", Values: " + etc.join(', '));
    };
    myFunction("a", "b", "c");
}).call(this);

Перечисление групповых аргументов в  любом списке аргументов функции

myFunction = (first, middles..., last) ->

   parts = []

   if first?
       parts.push(first.toUpperCase())
   for middle in middles
       parts.push(middle.toLowerCase())
   if last?
       parts.push(last.toUpperCase())
    parts.join('/')

console.log(myFunction("a", "b", "c"))

Аргументы можно подставлять в функции и виде массива аргументов (arr...)

myFunction = (etc...) ->
    console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"

a = ["a", "b", "c"]

myFunction(a...)

Результат "Length: 3, Values: a, b, c"

Массивы

При объявлении массива, если элементы располагаются на отдельных строках, то можно не ставить запятые!

myArray = [
  "a"
  "b"
  "c"
]

console.log myArray

Допускается использование одновременно запятых и новых строк без запятых.

myArray = [
  "a", "b", "c"
  "d", "e", "f"
  "g", "h", "i"
]

Проверка нахождения значения в массиве

nyArray = ["a", "b", "c"]

if "b" in myArray
    console.log "I found b"

unless "d" in myArray
    console.log "d was nowhere to be found"

Обмен значениями переменных

x = "X"
y = "Y"

[x, y] = [y, x]

console.log "x is #{x}" # x = "Y"
console.log "y is #{y}" # y = "X"

Присвоение значений множеству переменных сразу через массив

rack = () ->
    [200, {"Content-Type": "text/html"}, "Hello, World!"]

[status, headers, body] =  rack()

console.log "status is #{status}"
console.log "headers is #{JSON.stringify(headers)}"
console.log "body is #{body}"

Внимание! При присваивании с перестановкой и множественном присваивании не нада предварительно объявлять переменные, участвующие в этой операции. Компилятор сам позаботится об этом.
Для присваивания множества значений можно использовать синтаксис групповых аргументов.

myValues = [1, 2, 3, 4, 5]

[start, middle..., end] =  myValues

console.log start
console.log middle
console.log end

Если число значений в массиве будет меньше, чем число переменных, то у лишних переменных будет значение "undefined".
Если число переменных будет меньше, чем значений в массиве, то лишние значения останутся никому не присвоенными.

Диапазоны.

Создание массивов с диапазонами чисел [..] включая последнее число.

myArray = [1..10]

console.log myArray # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Создание массивов с диапазонами чисел [...] исключая последнее число.

myArray = [1...10]

console.log myArray # [1, 2, 3, 4, 5, 6, 7, 8, 9]

Создание массивов со значениями чисел в порядке убывания.

myArray = [10..1]
myArray = [10...1]

Вырезание элементов из массива

myArray = [1..10]
partOfArray = myArray[0..2]  # var partOfArray = myArray.slice(0, 3);
partOfArray = myArray[0...3] # var partOfArray = myArray.slice(0, 3);
partOfArray = myArray[4...8] # var partOfArray = myArray.slice(4, 8);

Замена сразу целой группы значений внутри массива

myArray = [0..10]
myArray[4..7] = ["a", "b", "c", "d"]

console.log myArray # [1, 2, 3, 4, "a", "b", "c", "d", 9, 10]

Вставка значений в середину массива, используя -1

myArray = [0..10]
myArray[4..-1] = ["a", "b", "c", "d"]

console.log myArray # [1, 2, 3, 4, "a", "b", "c", "d", 5, 6, 7, 8, 9, 10]

Создание пустого объекта

obj = {}

Создание объекта с данными

obj =
    firstName: "Mark"
    lastName: "Bates"

console.log obj

Внимание! Если свойства объекта располагаются на отдельных строках, то из не надо отделять запятыми (,), также можно в этом случае не писать фигурные скобки.
Хотя использование фигурных скобок мне кажется удобным.

obj = {
    firstName: "Mark"
    lastName: "Bates"
}

console.log obj

Добавление в объект функции

obj = {
    firstName: "Mark"
    lastName: "Bates"
    fullName: () ->
        return "#{@firstName} #{@lastName}"
}

console.log obj

Иногда объект строится так

foo = "FOO"
bar = "BAR"

obj = {
    foo: foo
    bar: bar
}

Эту процедуру можно упростить, просто записав

foo = "FOO"
bar = "BAR"

obj = {
    foo
    bar
}

Внимание! При таком способе записи использование фигурных скобок обязательно!

При использовании объекта в качестве аргумента функции использование фигурных скобок необязательно.

myFunction = (obj) ->
    console.log obj

myFunction(foo: "FOO", bar: "BAR")

это аналогично

myFunction({foo: "FOO", bar: "BAR"})

Хотя я бы скобки писал. Так понятнее.

Присвоение группы значений из объекта переменным (подобно тому как мы делали с массивами)

book = {
    title: "Programming"
    author: "Mark Bates"
    chapter1: {
        name: "Part 1"
        pageCount: 33
    }
    chapter2: {
        name: "Part 2"
        pageCount: 40
    }
}

{author, chapter1: {name, pageCount}} = book

console.log "Author #{author}"
console.log "Chapter 1 #{name}"
console.log "Page Count #{pageCount}"

Итерации по элементам массива.

for <переменная_цикла> in <массив>
    <ваш_код>

myLetters = ["a", "b", "c"]

for letter in myLetters
    console.log letter

for x in [1..5]
    console.log x

Увеличение итерационного значения в цикле через шаг приращения by

myLetters = ["a", "b", "c"]

for letter in myLetters by 2
    console.log letter

После ключевого слова by можно указать любое число (например -1) или функцию (например by func()), а цикл for будет выполнять итерпции по массиву с указанным шагом.

С помощью ключевого слова when можно добавить в цикл условие.

a = [1..10]

for num in a when num < 5
    console.log num

это аналогично такому коду

for num in a
    if num < 5
        console.log num

Итерации по атрибутам объектов.

for <переменная_для_ключа>, <переменная_для_значения> of <объект>
    <ваш_код>

person = {
    firstName: "Mark"
    lastName: "Bates"
}

for key, value of person
    console.log "#{key} is #{value}"

Важно! Отличия итераций по элементам объекта от итераций по элементам массива.
В итерации по атрибутам объекта необходимо объявить переменную для ключа и переменную для значения. И вместо слова in используется слово of.
Ключевое слово by в итерации по элементам объектов не применяется.
Но ключевое слово whene в итерации по элементам объектов может использоваться.

person = {
    firstName: "Mark"
    lastName: "Bates"
}


for key, value of person when value.lenth < 5
    console.log "#{key} is #{value}"

это аналогично такому коду

for key, value of person
    if value.length < 5
        console.log "#{key} is #{value}"

Перечисление свойств принадлежащих только данному объекту, а не его прототипу, через for own (аналог .hasOwnProperty в JavaScript).

person = {
    firstName: "Mark"
    lastName: "Bates"
}

Object.prototype.dob = new Date();

for own key, value of person
    console.log "Only this object #{key} is #{value}. Not prototype!"

Такой цикл обнаруживает только атрибуты явно объявденные в данном объекте.

В coffeescript помимо стандартного цикла while можно использовать его противоположность - цикл until
Цикл while продолжает выполнять операции, пока условное выражение остается истинным - true.
Цикл until продолжает выполнять операции, пока условное выражение остается ложным - false.

until index++ >= numberOfTimes
    console.log "Not now"

Генераторы

Генераторы - это обычные циклы, в которых блок кода находится в той же строке, что и конструкция цикла.

Простой пример генератора

myLetters = ["a", "b", "c"]

console.log letter.toUpperCase() for letter in letters

Генераторы можно использовать для сохранения значений, получаемых в цикле.
Заключая инструкцию генератора в круглые скобки, можно сохранить результаты в другой переменной.

Сохраним преобразованные значения в новом массиве upLetters

myLetters = ["a", "b", "c"]
upLetters = (letter.toUpperCase() for letter in myLetters)

console.log upLetters

Можно также сохранять значения в переменную в многострочной версии цикла for

myLetters = ["a", "b", "c"]

upLetters = for letter in myLetters
    letter.toUpperCase()

console.log upLetters

Однако код генераторов трудно читать и понимать.

Циклы с ключевым словом do().

Ключевое слово do() удобно использовать вместе с таймерами, чтобы не потерять значение переменных, получаемых в цикле.

fox x in [1..5]
    do(x) ->
        setTimeout() ->
            console.log x

Ключевое с лово do() - сделать - создает функцию-обертку вокруг программного кода, реализующего тело цикла, которая принамает занчение переменной, имеющееся на момет её вызова.

Что эквивалентно следующему коду в JavaScript.

var func = function(x){
    return setTimeout(function(){
        console.log(x);
    }, 1);
};

for (var x = 1; x <= 5, x++) {
    func(x);
}

Классы

Создание пустого класса

class Employee

Результат

(function(){
    var Employee = (function (){
        function Employee(){}
        return Employee;
    })();
}).call(this);

Создание экземпляров класса

class Employee

emp1 = new Employee()
emp2 = new Employee()

Внимание! При использовании ключевого слова new круглые скобки после названия класса ставить необязательно. Хотя так понятнее.

Создание функциий внутри класса

class Employee
    myFunc: (year: 1976, month = 7) ->
        return new Date()

emp1 = new Employee()
console.log emp1.myFunc()

Создание класса с функцией constructor

В coffeescript при создании класса можно определить функцию конструктор constructor, которая автоматически вызывается при создании нового экземпляра объект.
Функция constructor - это обычная функция, которая просто автоматически вызывается в момент создания объекта.

class Employee
    constructor: () ->
        console.log "Object created"
    myFunc: (x) ->
        console.log x

emp1 = new Emloeyee() # "Object created"
emp2 = new Emloeyee() # "Object created"

Функция constructor позволяет быстро заполнить объект его индивидуальными данными сразу при его создании.

Создание класса с атрибутами

class Employee
    constructor: (name) ->
        @name = name
    myFunc: () ->
        console.log @name

emp1 = new Employee("Boris")
emp1.myFunc()

Определени функции constructor можно сократить

class Employee
    constructor: (@name) ->
    myFunc: () -> console.log "My name: #{@name}"
    myFunc2: () ->
        @myFunc() # Вызвать функцию this.myFunc()

Можно подставлять несколько значений

class Employee
    constructor: (@name, @surname) ->
    myFunc: () -> console.log "My name: #{@name} and surname: #{@surname}"
    myFunc2: () ->
        @myFunc() # Вызвать функцию this.myFunc()


Можно подставлять массив или объект

class Employee
    constructor: (@attributes) ->
    myFunc: () -> console.log "My name: #{@attributes.name} and surname: #{@attributes.surname}"
    myFunc2: () ->
        @myFunc() # Вызвать функцию this.myFunc()

emp1 = new Empoyee({name: "Boris", surname: "Viktorov"})

Наследование классов

class Employee
    constructor: (name) ->
        @name = name
    myFunc: () ->
        console.log @name

class Manager extends Employee

emp1 = new Employee({name: "Mark", date: new Date(), salary: 5000})
emp1.myFunc()

manager = new Manager({name: "Bob", date: new Date(), salary: 70000})
manager.myFunc()

Наследовать классы также можно с использование пространства имен.

class Responder.Update extends MyClass.Show

Переопределение атрибутов в наследуемых классах

class Employee
    constructor: (name) ->
        @name = name
    myFunc: () ->
        console.log @name

class Manager extends Employee
    myFunc: () ->
        console.log "This is a manager"

Как видно при наследовании атрибуты могут просто перезаписываться. Так же могут добавляться новые атрибуты.

Вызов родительской функции в наследуемом классе через ключевое слово super

class Employee
    constructor: (name) ->
        @name = name
    myFunc: () ->
        console.log @name

class Manager extends Employee
    myFunc: () ->
        super # тут вызывается оригинальная функция myFunc() из класса Employee
        console.log "This is a manager"

Вызов super можно выполнять в любой точке функции, переопределяющей её!
Вызывая функцию super нет необходимости передавать ей аргументы явно. По умолчанию, все аргументы, полученные переопределяющей функцией, автоматически будут переданы функции super.
Однако функции super можно передавать аргументы и явно.

Функции класса

Функции класса не требуют для их вызова наличия (предварительного создания) экземпляра класса.
Они могут использоваться для подсчета числа созданных объектов или для организации пространства имен для своих функций.
Примером такой функции в JavaScript служит Math.random(). Чтобы получить случайное число не нужно создавать новый объект Math.
Math задает лишь простарнство имен для функции random().

Функции класса создаются при помощи символа @ (this) перед названием функции.
Ссылка this будет ссылаться на класс Employye, а не на конкретный экземпляр класса.
Внутри функции класса допускает использовать только другие функции и атрибуты класса.
При работе с функциями и атрибутами класса ключевое слово super имеет ограниченное примение. super можно использовать только для вызова оригинальной функции, переопределенной в дочернем классе.
Но, если оригинальная функция, вызываемая с помощью ключевого слова super, ссылается на какие-либо атрибуты класса, то это приведет к появлению большой ошибки во время выполнения кода.
Поскольку атрибуты класса Employee будут скрыты от класса Manager, то он не сможет напрямую обратиться к родительским атрибутам и произойдет ошибка.

class Employee
    constructor: () ->
        Employee.hire(@)
    @hire: (employee) ->
        @allEmployees ||= []
        @allEmployees.push(employee)
    @total: () ->
        console.log "There are #{@allEmployees.length} employees."

new Employee()
new Employee()
new Employee()

Employee.total() # There are 3 employees.

Важно! Лучше избегать использование ключевого слова super в функциях класса.
Лучше делать все функции самодостаточными, автономными и независимыми от других функций!

Функции прототипа

Добавление функций и атрибутов во все экземпляры объектов класса через прототип prototype.

В coffeescript добавление через прототип делается с помощью символов (::).

myArray = [1..10]

try
    console.log myArray.size()
catch (error)
    console.log error

Array::size = () ->
    @length

console.log myArray.size()

Результат

(function(){
  var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  try {
     console.log(myArray.size());
  } catch (error) {
    console.log(error);
  }
  Array.prototype.size = function(){
    return this.length;
  };
  myArray.size();
}).call(this);

Важно! Оператор :: был введен исключительно для удобства записи. В coffescript можно писать по-старинке непосредственно атрибут prototype.

Отличие -> от =>

=> привязывает функцию (bind) к контексту её выполнения. То есть позволяет не терять данные при вызове функции, созданной внутри класса и если происходя асинхронные операции.
Подробнее об этом можно прочитать в статье Understanding JavaScript Function Invocation and this. Автор Yehuda Katz.

Функциии setTimeout и setInterval выполняются асинхронно. То есть функциия, запущенная ранее, может выполниться позже, чем функция, которая была запущена позже. Из-за этого могут потеряться данные.

Сборка coffeescript через Cake
Cake доступен сразу после установки coffeescript.

Все задания по сборке должны находится в файле Cakefile, который должен располагаться в папке, где должны выполняться задания. Обычно это корневой каталог проекта.
Код файла Cakefile должен быть написан на coffeescript.

Создание задания для Cake в Cakefile.

Чтобы определить задание для Cake следует вызвать функцию task(), автоматически добавляемую во все файлы Cakefile.
В первом аргумента функции task() передается имя задания, которое будет указываться в команде выполнения этого задания.
Во втором аргументе передается текстовое описание задания. Оно будет отображаться при выводе списка доступных заданий.
В последнем аргументе передается ссылка на функцию, которая будет вызвана для выполнения задания. Эта функция функция как раз будет выполнять всю работу, связанную с данным заданием.

task ("greet", "Say Hello!", () -> console.log "Hello World!")

Результат

(function(){
    task("greet", "Say Hello!", function () {console.log "Hello World!";});
}).call(this);

Чтобы вывести список доступных заданий можно воспользоваться утилитой командной строки

> cake

Если теперь запустить Cakefile, то в консоли будет выведено имя задания и описание задания:

Cakefile defines the  folowing tasks:
cake greet #Say Hello!

Для запуска задания в консоли нужно набрать cake и имя задания

> cake greet

В результате в консоли будет выведено:

Hello World!

Передача аргументов в задание через функцию option.

option принимает 3 аргумента:
Первый аргумент - краткая форма параметра -n.
Второй аргумент - длинная форма параметра --name.
Третий аргумент - описание значения параметра.

Изменим код Cakefile

option ('-n', '--name [NAME]', 'name you want to greet')

task ("greet", "Say hi to someone", (options) ->
    message = "Hello "
    if options.name?
        message += options.name
    else
        message += "World"
    console.log message
)

Результат

(function(){
    options('-n', '--name [NAME]', 'name you want to greet');
    task ("greet", "Say hi to someone", function (options) {
      var message = "Hello ";
      if (options.name !== null) {
          message += options.name;
      } else {
          message += "World";
      }
      console.log (message);
  });
}).call(this);

В этой реализации задания вызывается функция option, которой передается 3 аргумента.
-n - краткая форма
--name [NAME] - длинная форма ([NAME] сообщает, что здесь ожидается некоторое значение. Без него будет сгенерирована ошибка.)
'name you want to greet' - описание

Важной! Значение перрвого аргумента не является обязательным, его можно просто заменить на null в первом аргументе.

Выполним команду cake

> cake

Уидим в консоли

Cakefile defines the  folowing tasks:
cake greet #Say hi to someone
    -n, --name   name you want to greet

Важно! Параметры задания не связаны с определенными значениями -  они определяются для всех заданий сразу!
Если в дополнение к заданию greet определить еще одно задание, то оба они смогут принимать параметр --name.
Хотя это и не большая проблема, необходимо с осторожностью подходить к выбору имен параметров и сопровождать их правильными описаниями.

Выполнить задание greet с параметром --name можно следующим образом.

> cake -n Mark greet

Важно! Все параметры помещаются в командной строке преред именем задания! Иначе вы получите сообщение об ошибке!

Вывод

Hello, Mark

Если нужно сделать передачу параметра обязательным, то для этого необходимо вручную организовать проверку параметра в определении задания и возбудить ошибку в случае его отсутствия.

option ('-n', '--name [NAME]', 'name you want to greet')

task ("greet", "Say hi to someone", (options) ->
    throw new Error("[NAME] is required") unless options.name?
    console.log "Hello, #{options.name}"
)

Вызов заданий из других заданий c помощью функции invoke()

task ("clean", "Clean up build directories", () -> console.log "cleaning up...")
task ("build", "Build the project files", () -> console.log "building...")
task ("package", "Clean, build and package the project", () ->
    invoke("clean")
    invoke("build")
    console.log "packaging..."
)

Вывод cake

> cake

Cakefile defines the following tasks:
cake clean # Clean up build directories
cake build # Build the project files
cake package # Clean, build and package the project

Выполнить 2 задания

> cake clean build

Выполнить 1 задание с вызовом двух предыдущих

> cake package

Внимание! Когда внутри одного задания вызваются другие задания, то они выполняются асинхронно. Такое поведение может приводить к ошибкам!
Тоже происходит, когда одной командой cak выполняетс цепочка заданий. Они все будут выполняться асинхронно, а не в порядке их вызова.

Разработка через тестирование
Сначала создаются контрольные примеры или тесты, а затем уже пишется сам код.

Хорошая статья на эту тему How to Become a Test-driven Developer

Установка Jasmine для тестирования кода

Установить jasmine-headless-webkit. Но он требует Ruby.

Работа с Jasmine

Создайте новую папку проекта.

mkdir project

Перейдите в нее.

cd project

Выполните команду

jasmine init

В папке project появится следующая структура

public/
    javascripts/
           Player.js
           Song.js
Rakefile
spec/
     javascripts/
          helpers/
              SpecHandler.js
          PalyerSpec.js
     support/
          jasmine.yml
          jasmine_config.rb
          jasmine_runner.rb

Проверяем настройки командой

jasmine-headless-webkit -c

На экране должно появиться

Running Jasmine specs...
.....
PASS: 5 tests, 0 failures, 0.009 secs.

Создадим файл Cakefile

exec = require('child_process').exec
tesk ("test", (options) =>
    exec ("jasmine-headless-webkit -c", (error, stdout, stdrr) ->
        console.log stdout
    )
)

Первая строка задания импортирует модуль child_process Node.js
Функци exec() позволяет выполнять команды в консоли и захватывать вывод этих команд.
Ниже мы создали задаине "test", которое выполняет команду "jasmine-headless-webkit -c", запускающую тесты.
По окончании тестирования вызывается функция обратного вызова и результаты тестов выводятся в консоль.

Теперь выполним наши тесты.

> cake test

Почистит проект

Удалим папки и файлы, чтобы получить следующую структуру проекта

src/
Rakefile
spec/
    javascripts/
          helpers/
    support/
           jasmine.yml
           jasmine_config.rb
           jasmine_runner.rb

Изменим настройки в файле spec/javascripts/helpers/support/jasmine.yml

src_files:
    - "**/*.coffee"
helpers:
    - "helpers/**/*.coffee"
spec_files:
    - "/**/*_spec.coffee"
src_dir: "src"
spec_dir: spec/javascripts

Теперь все готово к использованию Jasmine

Создадим в папке spec файл calculator_spec.coffee

Запишем в файле наши тесты

Прежде всего необходимо создать блок описания describe. Этот блок определяет объект испытаний.
Обычно в этом блоке описываются классы и функции.
Первым аргументом describe является строка, представляющая объект испытаний.
Во втором аргументе передается функция, содержащая все тесты, связанные с данным объектом испытаний.
Тесты определяются внутри функции it.
Блок it принимает 2 аргумента.
Первый аргумент - строка, которая описывает, что планируется сделать в ходе теста.
Второй аргумент - функция, которая содержит утверждения, истинность которых требуется проверить.
Тестирование выполняется с помощью методов сопоставления.
Если метод возвращает true, то считается, что тест пройден, в противном случае считается, что тест не пройден.

describe ("Calculator", () ->
    it ("does something", () ->
        expect(1+1).toEqual(2)
        expect(1+1).not.toEqual(3)
    )
)

Модульное тестирование
Каждый внутренний блок describe тестирует свою отдельную функцию.
В блоках it определяется реализация тестов данной функции

describe ("Calculator", () ->
    describe("#add", () ->
        it ("add two numbers", () ->
            calculator = new Calculator()
            expect(claculator.add(1, 1)).toEqual(2)
        )
    )
    describe("#substract", () ->
        it ("substracts two numbers", () ->
            calculator = new Calculator()
            expect(claculator.substract(10, 1)).toEqual(9)
        )
    )
    describe("#multiply", () ->
        it ("multiplies two numbers", () ->
            calculator = new Calculator()
            expect(claculator.multiply(5, 4)).toEqual(20)
        )
    )
    describe("#divide", () ->
        it ("divides two numbers", () ->
            calculator = new Calculator()
            expect(claculator.devide(20, 5)).toEqual(4)
        )
    )
)

Внимание! Символом (#) мы условно обозначаем, что тестируем функцию экземпляра класса.
Для описания функций класса мы будем использовать символ точки (.)

Создадим в папке src файл calculator.coffee

class @Calculator
    add: (a, b) -> a+b
    substract: (a, b) -> a-b
    multiply: (a, b) -> a*b
    devide: (a, b) -> a/b

Запуск тестов

> cake test

Использование повторяющегося кода в тестах через beforeEach (доступно также afterEach)

describe ("Calculator", () ->
    beforeEach (() ->
      @calculator = new Calculator()
    )
    describe("#add", () ->
        it ("add two numbers", () ->
            expect(@claculator.add(1, 1)).toEqual(2)
        )
    )
    describe("#substract", () ->
        it ("substracts two numbers", () ->
            expect(@claculator.substract(10, 1)).toEqual(9)
        )
    )
    describe("#multiply", () ->
        it ("multiplies two numbers", () ->
            expect(@claculator.multiply(5, 4)).toEqual(20)
        )
    )
    describe("#divide", () ->
        it ("divides two numbers", () ->
            expect(@claculator.devide(20, 5)).toEqual(4)
        )
    )
)

Внимание! Область действия функций beforeEach и afterEach простирается вниз до конца текущего блока describe, распространяясь на все вложенные блоки describe, независимо от глубины их вложенности!
Функций beforeEach может быть сколько угодно и они могут помещаться на любой уровень вложенности.

Создание собственных методов сопоставления

Чтобы определеить собственный метод сопоставления прежде необходимо определить функцию beforeEach.
Внутри beforeEach следует вызваеть встроенную функцию addMatches. Она принимает объект, содержащий имена добавляесых методов сопоставления и функции, реализующие их.
Собственный метод сопоставления должени возвращать либо true, либо false.

В папке spec/javascripts/helpers/directory файл с именем to_be_scientific.coffee

beforeEach () ->
    @addMatchers
        toBeScientific: () ->
            @actual.scientific is true

Результат

(function(){
    beforeEach(function(){
        return this.addMatchers({
            toBeScientific: function(){
                return this.actual.scientific === true;
            }
        });
    });
}).call(this);

Внимание! Выбор имени файла не имеет большого значения, но если имя совпадает с названием метода, то проще определить, что внутри файла.
Методы сопоставления необязательно помещать в отдельные файлы. Их можно определить в одном общем файле. Но разделение делает код проще.
Одноразовые методы сопоставления можно определять в тестах внутри блоков describe.

Собственный метод сопоставления можно задействовать в тестах.

describe ("Calculator", () ->
    describe("scientific mode", () ->
        it ("is in scientific mode", () ->
            calculator = new Calculator)
            expect(@claculator).toBeScientific()
        )
    )
)

Node JS
Команда coffee может выполнять файлы с кодом для Node JS.

> coffee file.coffee

2 комментария:

  1. If - Else If - Else

    today = "Monday"
    if today is "Sunday"
    console.log "Today is Sunday"
    else if if today is "Saturday"

    //опечатка в последней строчке - второй if не нужен

    ОтветитьУдалить