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.

159 lines
5.2KB

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