1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
	}
}