clignore 0.1.2

Simple CLI tool to find .gitignore files based on your language or framework
Documentation
use auth_token::AuthToken;
use choice_presenter::{ChoicePresenter, ChoiceResult};
use error_box::ErrorBox;
use file_finder::FileFinder;
use file_maker::FileMaker;
use github_request_maker::GitHubRequestMaker;
use request_maker::RequestMaker;
use std::io;

mod auth_token;
mod choice_presenter;
mod error_box;
mod file_finder;
mod file_maker;
mod github_request_maker;
mod github_url_builder;
mod request_maker;
mod requester;
mod test_request_maker;

type RealRequestMaker = GitHubRequestMaker<RequestMaker>;

pub struct Controller {
	request_maker: RealRequestMaker,
}

impl Controller {
	pub fn new(token: Option<AuthToken>) -> Result<Controller, ErrorBox> {
		let request_maker = Self::create_request_maker(token)?;
		Ok(Controller { request_maker })
	}

	fn create_request_maker(token: Option<AuthToken>) -> Result<RealRequestMaker, ErrorBox> {
		let requester = RequestMaker::new(token);
		let request_maker = GitHubRequestMaker::new(requester);

		// TODO: Handle unauthenticated error when we see limit has hit 0
		if let Some(limit) = request_maker.too_close_to_limit()? {
			println!(
				"Warning: The GitHub API will only allow you to make {} more requests for the hour. Consider providing an authentication token.",
				limit
			)
		}

		Ok(request_maker)
	}

	pub fn show_is_authenticated(&self) -> Result<(), ErrorBox> {
		let is_authenticated = self.request_maker.is_authenticated()?;
		match is_authenticated {
			true => println!("According to the GitHub API, you are authenticated!"),
			false => println!("According to the GitHub API, you are not authenticated. Consider providing an authentication token.")
		};

		Ok(())
	}

	pub fn list_files(&self) -> Result<(), ErrorBox> {
		for file_name in self.request_maker.get_file_names()? {
			println!("{}", file_name);
		}

		Ok(())
	}

	pub fn find_files(&self, query: &str) -> Result<(), ErrorBox> {
		let file_names = self.request_maker.get_file_names()?;
		let results = FileFinder::find(&file_names, query);

		if results.len() == 1 {
			self.download_exact_match(&results[0])?;
		} else if !results.is_empty() {
			self.handle_multiple_matches(query, results)?;
		} else {
			println!("No matches found for '{}'", query);
		}

		Ok(())
	}

	fn download_exact_match(&self, file_name: &str) -> Result<(), ErrorBox> {
		println!("Found match '{}'\nDownloading...", file_name);

		let content = self.request_maker.get_file(file_name)?;
		FileMaker::make_file(".gitignore", &content)?;

		println!("Downloaded .gitignore for '{}'", file_name.replace(".gitignore", ""));
		Ok(())
	}

	fn handle_multiple_matches(&self, query: &str, results: Vec<String>) -> Result<(), ErrorBox> {
		println!("Several matches found for '{}':\n", query);

		let choice_presenter = ChoicePresenter::new(results);
		println!("{}\n", choice_presenter.present_choices());

		loop {
			match self.get_choice(&choice_presenter) {
				Ok(choice) => match choice {
					Some(choice) => return self.download_exact_match(&choice),
					None => return Ok(()),
				},
				Err(_) => {
					println!("Invalid input, please try again.");
					continue;
				}
			}
		}
	}

	fn get_choice<'c>(&self, choice_presenter: &'c ChoicePresenter) -> ChoiceResult<'c> {
		println!(
			"Which do you want to use (0 to cancel)? [0-{}]:",
			choice_presenter.len()
		);

		let input = self.get_choice_input();
		choice_presenter.select_choice(&input)
	}

	fn get_choice_input(&self) -> String {
		let mut choice = String::new();
		io::stdin().read_line(&mut choice).unwrap();
		choice
	}
}