C++でTODOアプリ作ってみた

C++の勉強のために、コマンドライン上で動くTODOアプリを作りました。MySQL5.7を使います。macで作っています。

# Github

作ったものは[これ](https://github.com/endoyuta/cpptodo)です。

## 開発環境

- C++ (Apple LLVM version 8.1.0 (clang-802.0.42))
- MySQL5.7.18
- Mac Sierra

## インストール

### 1. MySQLでDB・テーブルを作成
MySQLでデータベースを作成し、下記tasksテーブルを作成します。

```
mysql> desc tasks;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| task       | varchar(100) | NO   |     | NULL    |                |
| status     | tinyint(1)   | NO   |     | 0       |                |
| important  | tinyint(1)   | NO   |     | 0       |                |
| sort_no    | int(6)       | YES  |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
```

### 2. main.cppのDB接続設定を編集

main.cppの下記を環境に合わせて編集します。

```main.cpp
#define DBHOST "localhost"
#define DBPORT 3306
#define DBUSER "root"
#define DBPASS "password"
#define DBNAME "todo"
```

### 3. makeします

コンパイルしているだけなので、todoという実行ファイルがカレントディレクトリに作成されます。

```
$ make
```

makefileはmacの環境用になっているので、環境に合わせて修正が必要になります。


## 使い方

下記はtodoをpathに追加した前提です。

todo helpでhelpが表示されます。

```
$ todo help
g <count>	show Incomplete <count> tasks
a <title>	add a <title> task
ao <title>	add a <title> task and check important
f <id>		finish the task <id>
r <id>		revert the task <id>
on <id>		check important to the <id> task
off <id>	remove check important to the <id> task
del <id>	delete the <id> task
h <count>	show completed <count> tasks 
clear		delete all completed tasks 
truncate	delete all tasks 
```

`todo`でタスク一覧が表示されます。

```
$ todo
5 *	プログラム作る
7  	Go勉強する
6  	C++勉強する
4  	歯を磨く
2  	寝る
```

`todo a`でタスクを追加します。`todo ao`でタスクの追加と同時に重要マークをつけます。

```
$ todo a "new task"
$ todo
5 *	プログラム作る
8  	new task
7  	Go勉強する
6  	C++勉強する
4  	歯を磨く
2  	寝る
```

`todo f`でタスクを完了にします。

```
$ todo f 2
$ todo
5 *	プログラム作る
8  	new task
7  	Go勉強する
6  	C++勉強する
4  	歯を磨く
```

`todo on`で重要マークがつけられます。重要なものは上に表示されます。`todo off`で重要マークを外せます。

```
$ todo on 8
$ todo
8 *	new task
5 *	プログラム作る
7  	Go勉強する
6  	C++勉強する
4  	歯を磨く
```
`todo h`で完了したタスク一覧が表示されます。

```
$ todo h
2  	寝る
1  	夕飯を食べる
3  	起きる
```

`todo d`で不要なタスクを削除できます。

```
$ todo d 3
$ todo h
2  	寝る
1  	夕飯を食べる
```

`todo r`で未完了状態に戻せます。
`todo clear`で完了済みタスクを全消去します。
`todo truncate`で、全タスクを削除し、初期化します。

## コード

```main.cpp
#include <iostream>
#include <mysql/mysql.h>
#include <boost/format.hpp>

#define DBHOST "localhost"
#define DBPORT 3306
#define DBUSER "root"
#define DBPASS "password"
#define DBNAME "todo"

using std::string;

void help()
{
	std::cout << "g <count>\tshow Incomplete <count> tasks\n";
	std::cout << "a <title>\tadd a <title> task\n";
	std::cout << "ao <title>\tadd a <title> task and check important\n";
	std::cout << "f <id>\t\tfinish the task <id>\n";
	std::cout << "r <id>\t\trevert the task <id>\n";
	std::cout << "on <id>\t\tcheck important to the <id> task\n";
	std::cout << "off <id>\tremove check important to the <id> task\n";
	std::cout << "del <id>\tdelete the <id> task\n";
	std::cout << "h <count>\tshow completed <count> tasks \n";
	std::cout << "clear\t\tdelete all completed tasks \n";
	std::cout << "truncate\tdelete all tasks \n";
}

class mysql
{
  protected:
	MYSQL *conn;
	MYSQL_RES *res;
	MYSQL_ROW row;
	void execute(string sql);

  public:
	mysql();
	~mysql();
};

mysql::mysql()
{
	conn = mysql_init(NULL);
	if (!mysql_real_connect(conn, DBHOST, DBUSER, DBPASS, DBNAME, DBPORT, NULL, 0))
	{
		fprintf(stderr, "%s\n", mysql_error(conn));
		exit(1);
	}
}

mysql::~mysql()
{
	mysql_free_result(res);
	mysql_close(conn);
}

void mysql::execute(string sql)
{
	if (mysql_query(conn, sql.c_str())) //todo sanitaize?
	{
		fprintf(stderr, "%s\n", mysql_error(conn));
		exit(1);
	}
	res = mysql_use_result(conn);
}

class todo : public mysql
{
	void check_id(int id);

  public:
	void get(int limit, string mode);
	void add(string task, string mode);
	void fin(int id, string mode);
	void star(int id, string mode);
	void del(int id);
	void clear();
	void truncate();
};

void todo::check_id(int id)
{
	if (!id)
	{
		std::cerr << "invalid id\n";
		exit(1);
	}
}

void todo::get(int limit = 20, string mode = "get")
{
	string sql = "select * from tasks where status = %1%";
	sql += " order by important desc,updated_at desc limit %2%";
	limit = !limit ? 20 : limit;
	int status = mode == "his" ? 1 : 0;
	sql = (boost::format(sql) % status % limit).str();
	execute(sql);
	string star;
	while ((row = mysql_fetch_row(res)) != NULL)
	{
		star = atoi(row[3]) ? " *" : "  ";
		std::cout << row[0] << star << "\t" << row[1] << std::endl;
	}
}

void todo::add(string task, string mode = "add")
{
	if (task == "")
	{
		std::cerr << "invalid task\n";
		exit(1);
	}
	int star = mode == "add" ? 0 : 1;
	string sql = "insert into tasks";
	sql += " (task, important, updated_at) values ('%1%', %2%, now())";
	sql = (boost::format(sql) % task % star).str();
	execute(sql);
}

void todo::del(int id)
{
	check_id(id);
	string sql = "delete from tasks where id = %1%";
	sql = (boost::format(sql) % id).str();
	execute(sql);
}

void todo::fin(int id, string mode = "fin")
{
	check_id(id);
	int status = mode == "rev" ? 0 : 1;
	string sql = "update tasks set status = %1%, important = 0,";
	sql += " updated_at = now() where id = %2%";
	sql = (boost::format(sql) % status % id).str();
	execute(sql);
}

void todo::star(int id, string mode = "on")
{
	check_id(id);
	int status = mode == "on" ? 1 : 0;
	string sql = "update tasks set important = %1%,";
	sql += " updated_at = now() where id = %2%";
	sql = (boost::format(sql) % status % id).str();
	execute(sql);
}

void todo::clear()
{
	execute("delete from tasks where status = 1");
}

void todo::truncate()
{
	execute("truncate table tasks");
}

int check_int(std::string str)
{
	try
	{
		return stoi(str);
	}
	catch (...)
	{
		return 0;
	}
}

void action(todo *td, string mode, string param)
{
	if (mode == "g")
	{
		td->get(check_int(param));
	}
	else if (mode == "a")
	{
		td->add(param);
	}
	else if (mode == "ao")
	{
		td->add(param, "star");
	}
	else if (mode == "d")
	{
		td->del(check_int(param));
	}
	else if (mode == "f")
	{
		td->fin(check_int(param));
	}
	else if (mode == "r")
	{
		td->fin(check_int(param), "rev");
	}
	else if (mode == "h")
	{
		td->get(check_int(param), "his");
	}
	else if (mode == "on")
	{
		td->star(check_int(param));
	}
	else if (mode == "off")
	{
		td->star(check_int(param), "off");
	}
	else if (mode == "clear")
	{
		td->clear();
	}
	else if (mode == "truncate")
	{
		td->truncate();
	}
	else
	{
		help();
	}
}

int main(int argc, char *argv[])
{
	todo td;
	if (argc < 2)
	{
		td.get();
	}
	else
	{
		action(&td, argv[1], argc < 3 ? "" : argv[2]);
	}
	return 0;
}
```

makefile

```makefile
LDLIBS   = -lm -lmysqlclient
FLAGS = -Wall -I/usr/local/include
LDFLAGS  = -L/usr/local/lib

todo: main.cpp
	c++ -O2 -o $@ $(LDLIBS) $(FLAGS) $(LDFLAGS) $^
```