You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
5.4KB

  1. use ansi_term::Colour::{Blue, Green, Red};
  2. use ansi_term::Style;
  3. use clap::ArgMatches;
  4. use serde_json;
  5. use std::convert::From;
  6. use std::io;
  7. use std::io::Write;
  8. use std::path::PathBuf;
  9. use tantivy;
  10. use tantivy::schema::Cardinality;
  11. use tantivy::schema::*;
  12. use tantivy::Index;
  13. use std::fs;
  14. pub fn run_new_cli(matches: &ArgMatches) -> Result<(), String> {
  15. let index_directory = PathBuf::from(matches.value_of("index").unwrap());
  16. run_new(index_directory).map_err(|e| format!("{:?}", e))
  17. }
  18. fn prompt_input<P: Fn(&str) -> Result<(), String>>(prompt_text: &str, predicate: P) -> String {
  19. loop {
  20. print!(
  21. "{prompt_text:<width$} ? ",
  22. prompt_text = Style::new().bold().fg(Blue).paint(prompt_text),
  23. width = 40
  24. );
  25. io::stdout().flush().unwrap();
  26. let mut buffer = String::new();
  27. io::stdin()
  28. .read_line(&mut buffer)
  29. .ok()
  30. .expect("Failed to read line");
  31. let answer = buffer.trim_end().to_string();
  32. match predicate(&answer) {
  33. Ok(()) => {
  34. return answer;
  35. }
  36. Err(msg) => {
  37. println!("Error: {}", Style::new().bold().fg(Red).paint(msg));
  38. }
  39. }
  40. }
  41. }
  42. fn field_name_validate(field_name: &str) -> Result<(), String> {
  43. if is_valid_field_name(field_name) {
  44. Ok(())
  45. } else {
  46. Err(String::from(
  47. "Field name must match the pattern [_a-zA-Z0-9]+",
  48. ))
  49. }
  50. }
  51. fn prompt_options(msg: &str, codes: Vec<char>) -> char {
  52. let options_string: Vec<String> = codes.iter().map(|c| format!("{}", c)).collect();
  53. let options = options_string.join("/");
  54. let predicate = |entry: &str| {
  55. if entry.len() != 1 {
  56. return Err(format!("Invalid input. Options are ({})", options));
  57. }
  58. let c = entry.chars().next().unwrap().to_ascii_uppercase();
  59. if codes.contains(&c) {
  60. return Ok(());
  61. } else {
  62. return Err(format!("Invalid input. Options are ({})", options));
  63. }
  64. };
  65. let message = format!("{} ({})", msg, options);
  66. let entry = prompt_input(&message, predicate);
  67. entry.chars().next().unwrap().to_ascii_uppercase()
  68. }
  69. fn prompt_yn(msg: &str) -> bool {
  70. prompt_options(msg, vec!['Y', 'N']) == 'Y'
  71. }
  72. fn ask_add_field_text(field_name: &str, schema_builder: &mut SchemaBuilder) {
  73. let mut text_options = TextOptions::default();
  74. if prompt_yn("Should the field be stored") {
  75. text_options = text_options.set_stored();
  76. }
  77. if prompt_yn("Should the field be indexed") {
  78. let mut text_indexing_options = TextFieldIndexing::default()
  79. .set_index_option(IndexRecordOption::Basic)
  80. .set_tokenizer("en_stem");
  81. if prompt_yn("Should the term be tokenized?") {
  82. if prompt_yn("Should the term frequencies (per doc) be in the index") {
  83. if prompt_yn("Should the term positions (per doc) be in the index") {
  84. text_indexing_options = text_indexing_options
  85. .set_index_option(IndexRecordOption::WithFreqsAndPositions);
  86. } else {
  87. text_indexing_options =
  88. text_indexing_options.set_index_option(IndexRecordOption::WithFreqs);
  89. }
  90. }
  91. } else {
  92. text_indexing_options = text_indexing_options.set_tokenizer("raw");
  93. }
  94. text_options = text_options.set_indexing_options(text_indexing_options);
  95. }
  96. schema_builder.add_text_field(field_name, text_options);
  97. }
  98. fn ask_add_field_u64(field_name: &str, schema_builder: &mut SchemaBuilder) {
  99. let mut u64_options = IntOptions::default();
  100. if prompt_yn("Should the field be stored") {
  101. u64_options = u64_options.set_stored();
  102. }
  103. if prompt_yn("Should the field be fast") {
  104. u64_options = u64_options.set_fast(Cardinality::SingleValue);
  105. }
  106. if prompt_yn("Should the field be indexed") {
  107. u64_options = u64_options.set_indexed();
  108. }
  109. schema_builder.add_u64_field(field_name, u64_options);
  110. }
  111. fn ask_add_field(schema_builder: &mut SchemaBuilder) {
  112. println!("\n\n");
  113. let field_name = prompt_input("New field name ", field_name_validate);
  114. let text_or_integer = prompt_options("Text or unsigned 32-bit integer", vec!['T', 'I']);
  115. if text_or_integer == 'T' {
  116. ask_add_field_text(&field_name, schema_builder);
  117. } else {
  118. ask_add_field_u64(&field_name, schema_builder);
  119. }
  120. }
  121. fn run_new(directory: PathBuf) -> tantivy::Result<()> {
  122. println!(
  123. "\n{} ",
  124. Style::new().bold().fg(Green).paint("Creating new index")
  125. );
  126. println!(
  127. "{} ",
  128. Style::new()
  129. .bold()
  130. .fg(Green)
  131. .paint("Let's define it's schema!")
  132. );
  133. let mut schema_builder = SchemaBuilder::default();
  134. loop {
  135. ask_add_field(&mut schema_builder);
  136. if !prompt_yn("Add another field") {
  137. break;
  138. }
  139. }
  140. let schema = schema_builder.build();
  141. let schema_json = format!("{}", serde_json::to_string_pretty(&schema).unwrap());
  142. println!("\n{}\n", Style::new().fg(Green).paint(schema_json));
  143. match fs::create_dir(&directory) {
  144. Ok(_) => (),
  145. // Proceed here; actual existence of index is checked in Index::create_in_dir
  146. Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
  147. Err(e) => panic!("{:?}", e),
  148. };
  149. Index::create_in_dir(&directory, schema)?;
  150. Ok(())
  151. }