diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..936d024 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true + +[*.json] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..db814bf --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "hrundel/node" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d48db5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +/node_modules +*.log diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f5357d5 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +save=true +save-exact=true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4909f83 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "6" diff --git a/README.md b/README.md index 07fbe88..133405a 100644 --- a/README.md +++ b/README.md @@ -1 +1,67 @@ -# javascript-task-2 \ No newline at end of file +# Задача «Телефонная книга» + +Перед выполнением задания внимательно прочитайте: + +- [О всех этапах проверки задания](https://github.com/urfu-2016/guides/blob/master/workflow/extra.md) +- [Как отправить пулл](https://github.com/urfu-2016/guides/blob/master/workflow/pull.md) +- [Как пройти тесты](https://github.com/urfu-2016/guides/blob/master/workflow/test.md) +- Правила оформления [javascript](https://github.com/urfu-2016/guides/blob/master/codestyle/js.md), [HTML](https://github.com/urfu-2016/guides/blob/master/codestyle/html.md) и [CSS](https://github.com/urfu-2016/guides/blob/master/codestyle/css.md) кода +- [Лекцию «Типы данных»](https://urfu-2016.github.io/javascript-slides/02-types/#/) + + +## Основное задание + +> Мы очень хотим, чтобы код вы написали сами, а не пользовались внешними библиотеками. + +Как известно, каждый уважающий себя разработчик должен в жизни сделать три вещи: +- [x] посадить DOM дерево +- [x] построить абстракцию +- [ ] ~~вырастить~~ написать телефонную книгу + +Предлагаем вам пройти легкий путь становления уважающего себя разработчика и реализовать для скрипта телефонной книги __phone-book.js__ ряд необходимых методов. + +Метод __add__ для добавления записей: +* На вход принимает «Телефон», «Имя» и «Электронную почту» +* Возвращает true или false в зависимости от успеха опереации +* Телефоны принимаются **только** в формате 5556667788 (без кода) + +Метод __update__ для обновления записей: +* На вход принимает «Телефон», «Имя» и «Электронную почту» +* Обновляет «Имя» и «Электронную почту» по заданному «Телефону» +* Возвращает true или false в зависимости от успеха опереации +* «Электронную почту» можно стереть (не передав последний параметр), а «Имя» – нет + +Метод __find__ для поиска записей: +* На вход принимает запрос в виде строки +* Ищет вхождение этой строки хотя бы в одно из полей «Телефон», «Имя» и «Электронную почту» +* Возвращает отсортированный по «Имени» массив строк в формате `name, phone, email` +* «Имя» и «Электронную почту» выводит как есть, а «Телефон» в формате `+7 (555) 666-77-88` +* Пустой запрос не должен ничего не находить +* Запрос «*» находит все записи + +Метод __findAndRemove__ для удаления записей: +* На вход принимает запрос в виде строки +* Находит (смотри __find__) и удаляет все найденные записи +* Возвращает число удаленных записей + +В файле _index.js_ вы можете найти примеры использования получившегося скриптика. + +## Дополнительное задание + +> Перед выполнением внимательно прочитайте [про особенности](https://github.com/urfu-2016/guides/blob/master/workflow/extra.md) + +По одной добавлять записи в книгу не очень удобно, поэтому будет здорово, если вы добавите в решение импорт данных из csv. Для этого реализуйте код метода __importFromCsv__. + +На вход метод принимает строку в формате csv. Если запись в телефонной книги уже есть – обновляет/дополняет её данными из csv строки. На выходе метод возвращает одно число добавленных/обновленных записей. + +Пример работы этого метода вы может отыскать в _index.js_ и в тестах. + +## Полезные ссылки + +- [Знакомимся с массивами](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array) +- [Пытаемся знакомиться с регулярными выражениями](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp) +- [Перебираем ключи объектов](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) +- [Метод indexOf для строк](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) +- [Метод slice для строк](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/slice) + +Позвони мне, позвони diff --git a/index.js b/index.js new file mode 100644 index 0000000..9b259f5 --- /dev/null +++ b/index.js @@ -0,0 +1,44 @@ +'use strict'; + +var phoneBook = require('./phone-book'); + +// Эти записи добавятся, вернется true +phoneBook.add('5554440044', 'Григорий', 'grisha@example.com'); +phoneBook.add('5552220022', 'Борис', 'boris@example.com'); +phoneBook.add('5551110011', 'Алекс'); +phoneBook.add('5553330033', 'Валерий', 'valera@example.com'); + +// Эти запись не добавятся +phoneBook.add('3330033', 'Неизвестный', 'unknown@example.com'); +phoneBook.add('5551110011', 'Алексей'); +phoneBook.add('5555550055'); + +// Обновление +phoneBook.update('5551110011', 'Алексей', 'alex@example.com'); +phoneBook.update('5553330033', 'Валерий'); + +// В следующих примерах вернутся все записи +console.info(phoneBook.find('*')); +console.info(phoneBook.find('555')); +// Вывод будет следующий +// [ +// 'Алексей, +7 (555) 111-00-11, alex@example.com', +// 'Борис, +7 (555) 222-00-22, boris@example.com', +// 'Валерий, +7 (555) 333-00-33', +// 'Григорий, +7 (555) 444-00-44, grisha@example.com' +// ] + +// Удаление +phoneBook.findAndRemove('@'); // returns 3 + +if (phoneBook.isStar) { + // Импортируем из csv + var csv = [ + 'Борис;5552220022;boris@example.com', + 'Григорий;5554440044;grisha@example.com', + 'Алексей;5551110011;alex@example.com', + 'Валерий;5553330033;valera@example.com', + 'Неизвестный;3330033;unknown@example.com' + ].join('\n'); + phoneBook.importFromCsv(csv); // returns 4 +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1400772 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "main": "phone-book.js", + "scripts": { + "lint": "eslint .", + "test": "eslint . && mocha *.spec.js" + }, + "dependencies": { + "eslint": "3.7.0", + "eslint-config-hrundel": "latest", + "mocha": "3.1.0" + } +} diff --git a/phone-book.js b/phone-book.js new file mode 100644 index 0000000..8867e12 --- /dev/null +++ b/phone-book.js @@ -0,0 +1,210 @@ +'use strict'; + +/** + * Сделано задание на звездочку + * Реализован метод importFromCsv + */ +exports.isStar = true; + +/** + * Телефонная книга + */ +var phoneBook = []; + +/** + * Проверка записи в телефонную книгу на корректность + * @param {String} phone + * @param {String} name + * @param {String} email + * @returns {Boolean} – true, если зпись является корректной; false в противном случае + */ +exports.isCorrectRecord = function (phone, name, email) { + var isCorrectPhone = (/\d{9}/.test(phone)) && (phone[0] === phone[1]) && + (phone[1] === phone[2]) && (phone[3] === phone[4]) && (phone[4] === phone[5]) && + (phone[6] === phone[7]) && (phone[8] === phone[9]); + var isCorrectName = (typeof name === 'string' && name.length> 0); + var isCorrectEmail = (typeof email === 'undefined') || (typeof email === 'string'); + + return (isCorrectEmail && isCorrectName && isCorrectPhone); +}; + +/** + * Проверка записи в телефонную книгу на уникальность + * @param {String} phone + * @returns {Number} – -1, если номер не зарегистрирован; номер записи в противном случае + */ +exports.identicalNumber = function (phone) { + for (var i = 0; i < phoneBook.length; i++) { + if (phoneBook[i].phone === phone) { + + return i; + } + } + + return -1; +}; + +/** + * Добавление записи в телефонную книгу + * @param {String} phone + * @param {String} name + * @param {String} email + * @returns {Boolean} – true, если если удалось добавить запись; false в противном случае + */ +exports.add = function (phone, name, email) { + if (exports.isCorrectRecord(phone, name, email) && exports.identicalNumber(phone) === -1) { + if (typeof email === 'undefined') { + phoneBook.push({ phone: phone, name: name }); + } else { + phoneBook.push({ phone: phone, name: name, email: email }); + } + + return true; + } + + return false; +}; + +/** + * Обновление записи в телефонной книге + * @param {String} phone + * @param {String} name + * @param {String} email + * @returns {Boolean} – true, если удалось обновить запись; false в противном случае + */ +exports.update = function (phone, name, email) { + var indexOfRecord = exports.identicalNumber(phone); + if (exports.isCorrectRecord(phone, name, email) && indexOfRecord> -1) { + phoneBook[indexOfRecord].name = name; + if (typeof email !== 'undefined') { + phoneBook[indexOfRecord].email = email; + } else { + delete phoneBook[indexOfRecord].email; + } + + return true; + } + + return false; +}; + +/** + * '1112223344' -> '+7 (111) 222-33-44' + * @param {String} phone + * @returns {String} телефон в нормализованном формате + */ +exports.normalPhone = function (phone) { + + return '+7 (' + phone.substr(0, 3) + ') ' + phone.substr(3, 3) + '-' + phone.substr(6, 2) + + '-' + phone.substr(8, 2); +}; + +/** + * Преобразование объекта - записи телфеонной книги в строку + * @param {Object} record + * @returns {String} запись в одну строку + */ +exports.makeRecordLine = function (record) { + var line; + if (record.email === undefined) { + line = record.name + ', ' + exports.normalPhone(record.phone); + } else { + line = record.name + ', ' + exports.normalPhone(record.phone) + + ', ' + record.email; + } + + return line; +}; + +/** + * Проверка на содержание подстроки + * @param {Object} record + * @param {String} query + * @returns {Boolean} true, если есть вхождение query в одно из полей record + */ +exports.haveRecordCoin = function (record, query) { + var key = Object.keys(record); + for (var i = 0; i < key.length; i++) { + if ((record[key[i]] !== undefined) && (record[key[i]].indexOf(query) !== -1)) { + + return true; + } + } + + return false; +}; + +/** + * Поиск записей по запросу в телефонной книге + * @param {String} query + * @returns {Array} отсортированный массив записей, не имеющих пересечений с аргументом + */ +exports.find = function (query) { + if (query === '') { + + return []; + } + var recordFound = []; + if (query === '*') { + for (var i = 0; i < phoneBook.length; i++) { + recordFound.push(exports.makeRecordLine(phoneBook[i])); + } + } + for (var j = 0; j < phoneBook.length; j++) { + if (exports.haveRecordCoin(phoneBook[j], query)) { + recordFound.push(exports.makeRecordLine(phoneBook[j])); + } + } + + return recordFound.sort(); +}; + +/** + * Удаление записей по запросу из телефонной книги + * @param {String} query + * @returns {Number} количество удаленных записей + */ +exports.findAndRemove = function (query) { + if (query === '') { + + return 0; + } + if (query === '*') { + var n = phoneBook.length; + phoneBook = []; + + return n; + } + var recordSave = []; + var countDeleted = phoneBook.length; + for (var i = 0; i < phoneBook.length; i++) { + if (!exports.haveRecordCoin(phoneBook[i], query)) { + recordSave.push(phoneBook[i]); + countDeleted--; + } + } + phoneBook = recordSave; + + return countDeleted; +}; + +/** + * Импорт записей из csv-формата + * @star + * @param {String} csv + * @returns {Number} – количество добавленных и обновленных записей + */ +exports.importFromCsv = function (csv) { + var records = csv.split('\n'); + var record; + var countUpdate = 0; + for (var i = 0; i < records.length; i++) { + record = records[i].split(';'); + if (exports.update(record[1], record[0], record[2]) || + (exports.add(record[1], record[0], record[2]))) { + countUpdate++; + } + } + + return countUpdate; +}; diff --git a/phone-book.spec.js b/phone-book.spec.js new file mode 100644 index 0000000..1b62a1a --- /dev/null +++ b/phone-book.spec.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict'; + +var assert = require('assert'); + +var phoneBook = require('./phone-book'); + +describe('phone-book', function () { + it('должен добавлять записи', function () { + assert.ok(phoneBook.add('5554440044', 'Григорий', 'grisha@example.com')); + assert.ok(phoneBook.add('5552220022', 'Борис', 'boris@example.com')); + assert.ok(phoneBook.add('5551110011', 'Алекс')); + assert.ok(phoneBook.add('5553330033', 'Валерий', 'valera@example.com')); + }); + + it('не должен добавлять неправильные записи', function () { + assert.ok(!phoneBook.add('3330033', 'Неизвестный', 'unknown@example.com')); + assert.ok(!phoneBook.add('5551110011', 'Алексей')); + assert.ok(!phoneBook.add('5555550055')); + }); + + it('должен обновлять существующие записи', function () { + assert.ok(phoneBook.update('5551110011', 'Алексей', 'alex@example.com')); + assert.ok(phoneBook.update('5553330033', 'Валерий')); + }); + + it('должен искать все записи по запросу "*"', function () { + assert.deepStrictEqual(phoneBook.find('*'), [ + 'Алексей, +7 (555) 111-00-11, alex@example.com', + 'Борис, +7 (555) 222-00-22, boris@example.com', + 'Валерий, +7 (555) 333-00-33', + 'Григорий, +7 (555) 444-00-44, grisha@example.com' + ]); + }); + + it('должен искать все записи по запросу "555"', function () { + assert.deepStrictEqual(phoneBook.find('555'), [ + 'Алексей, +7 (555) 111-00-11, alex@example.com', + 'Борис, +7 (555) 222-00-22, boris@example.com', + 'Валерий, +7 (555) 333-00-33', + 'Григорий, +7 (555) 444-00-44, grisha@example.com' + ]); + }); + + it('должен удалять элементы из телефонной книги', function () { + assert.strictEqual(phoneBook.findAndRemove('@'), 3); + }); + + if (phoneBook.isStar) { + it('должен экспортировать из cvs', function () { + var csv = [ + 'Борис;5552220022;boris@example.com', + 'Григорий;5554440044;grisha@example.com', + 'Алексей;5551110011;alex@example.com', + 'Валерий;5553330033;valera@example.com', + 'Неизвестный;3330033;unknown@example.com' + ].join('\n'); + assert.strictEqual(phoneBook.importFromCsv(csv), 4); + }); + } +});