├── assets ├── bible-tui.jpg └── en-blible-tui.png ├── src ├── main.cpp ├── start-def.hpp ├── unicode-theme.hpp ├── options.hpp ├── getverse.hpp ├── bible-tui.hpp ├── bible-tui.cpp ├── getverse.cpp └── options.cpp ├── .gitignore ├── CMakeLists.txt ├── LICENSE └── README.md /assets/bible-tui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terroo/bible-tui/HEAD/assets/bible-tui.jpg -------------------------------------------------------------------------------- /assets/en-blible-tui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terroo/bible-tui/HEAD/assets/en-blible-tui.png -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "options.hpp" 2 | 3 | int main(int argc, char** argv){ 4 | auto op = std::make_unique(); 5 | op->run_options(argc, argv); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/start-def.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "unicode-theme.hpp" 5 | 6 | typedef struct START { 7 | std::string lang; 8 | THEMES theme; 9 | UNICODE unicode; 10 | std::string book; 11 | } start_def; 12 | -------------------------------------------------------------------------------- /src/unicode-theme.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum THEMES { 4 | REAL, 5 | LOLCAT, 6 | ATOM, 7 | STYLE, 8 | THEME_INVALID 9 | }; 10 | 11 | enum UNICODE { 12 | DEFAULT, 13 | DOUBLE, 14 | ROUNDED, 15 | EMPTY, 16 | COUNT, 17 | MOLD_INVALID 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build 34 | -------------------------------------------------------------------------------- /src/options.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "unicode-theme.hpp" 7 | #include "bible-tui.hpp" 8 | 9 | class Options { 10 | //std::unique_ptr defs_options = nullptr; 11 | const std::string VERSION = "bible-tui v1.0"; 12 | public: 13 | Options(); 14 | THEMES parse_theme(const std::string& value); 15 | UNICODE parse_mold(const std::string& value); 16 | bool validate_book_format(const std::string& book); 17 | void run_options(int, char**); 18 | void help(); 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /src/getverse.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "start-def.hpp" 12 | 13 | class GetVerse { 14 | 15 | const std::string br_random = "https://dailyverses.net/pt/versiculo-aleatorio-da-biblia"; 16 | const std::string en_random = "https://dailyverses.net/random-bible-verse"; 17 | 18 | protected: 19 | GetVerse(const std::string& lang, const std::string& book); 20 | void run_verse(); 21 | std::string format(const std::string& str); 22 | std::string format_book(const std::string& str); 23 | std::string br, en, lang, book; 24 | 25 | public: 26 | std::string verse, capt; 27 | }; 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0) 2 | project(BibleTUI CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -ffast-math") 8 | 9 | 10 | include(FetchContent) 11 | FetchContent_Declare(json 12 | GIT_REPOSITORY https://github.com/nlohmann/json 13 | GIT_TAG v3.12.0 14 | ) 15 | 16 | FetchContent_MakeAvailable(json) 17 | 18 | # cURLpp + libcurl 19 | find_package(CURL REQUIRED) 20 | 21 | # Se cURLpp estiver instalado no sistema 22 | find_path(CURLPP_INCLUDE_DIR curlpp/cURLpp.hpp) 23 | find_library(CURLPP_LIBRARY NAMES curlpp) 24 | 25 | if (NOT CURLPP_INCLUDE_DIR OR NOT CURLPP_LIBRARY) 26 | message(FATAL_ERROR "cURLpp não encontrado") 27 | endif () 28 | 29 | include_directories(${CURL_INCLUDE_DIRS} ${CURLPP_INCLUDE_DIR}) 30 | 31 | file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/src/*.cpp) 32 | 33 | add_executable(bible-tui ${SOURCES}) 34 | 35 | target_link_libraries(bible-tui ${CURL_LIBRARIES} ${CURLPP_LIBRARY} nlohmann_json::nlohmann_json) 36 | 37 | install(TARGETS bible-tui DESTINATION bin) 38 | 39 | -------------------------------------------------------------------------------- /src/bible-tui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "getverse.hpp" 10 | #include "unicode-theme.hpp" 11 | #include "start-def.hpp" 12 | 13 | class BibleTUI : public GetVerse { 14 | 15 | static constexpr int MAX_WIDTH = 55; 16 | static constexpr int TEXT_OFFSET = 2; 17 | 18 | std::unique_ptr m_defs; 19 | 20 | std::array, COUNT> unicodes; 21 | std::array unicode; 22 | void set_unicodes(); 23 | 24 | std::array, STYLE> themes; 25 | std::array theme; 26 | void set_theme(); 27 | 28 | size_t utf8_length(const std::string& str); 29 | std::vector wrap_text(const std::string& text, int width); 30 | std::string pad_line(const std::string& line, int width, const std::string& content_color); 31 | std::string make_border(const std::string& left, const std::string& fill, 32 | const std::string& right, int count); 33 | 34 | public: 35 | BibleTUI(const start_def& defs); 36 | void run(); 37 | }; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bible TUI 2 | Get Bible Verses via Command Line 3 | 4 | ![Bible TUI](./assets/bible-tui.jpg) 5 | 6 | --- 7 | 8 | ## 🙏 `bible-tui` 9 | `bible-tui` is a command line utility that displays dynamic or selected verses from the Bible with different frames and color themes. Available in English and Portuguese. 10 | 11 | --- 12 | 13 | ## 📦 Dependencies 14 | + C++ Compiler: [GCC](https://gcc.gnu.org/) or [Clang](https://clang.llvm.org/) 15 | + [CMake](https://cmake.org/) 16 | + [cURL++](https://www.curlpp.org/) 17 | + [JSON++](https://github.com/nlohmann/json) 18 | 19 | Example on Ubuntu: 20 | ```sh 21 | sudo apt install build-essential cmake libcurlpp-dev nlohmann-json3-dev 22 | ``` 23 | 24 | --- 25 | 26 | ## 🚧 Build and Install 27 | ```bash 28 | git clone https://github.com/terroo/bible-tui 29 | cd bible-tui 30 | cmake . -B build 31 | cmake --build build 32 | sudo cmake --install build 33 | ``` 34 | 35 | --- 36 | 37 | ## 💼 Usage and Examples 38 | 39 | ![Examples](./assets/en-blible-tui.png) 40 | 41 | Help: `bible-tui --help` 42 | 43 | ```bash 44 | Usage: 45 | bible-tui [options] 46 | 47 | Options: 48 | --lang=pt|en Set the language. 49 | --theme=real|lolcat|atom|style Set the color theme. 50 | --mold=default|double|rounded|empty Set the border style. 51 | --book=name:num-num Set the book and verse. 52 | Examples: 53 | [EN]psalms:23-1 | [PT]salmos:23-1 54 | --help, -h Show this message. 55 | --version, -v Show version info. 56 | 57 | ``` 58 | 59 | --- 60 | 61 | > **`bible-tui`** is still in early versions, if you find any *bugs*, please open an [issue](https://github.com/terroo/bible-tui/issues). 62 | -------------------------------------------------------------------------------- /src/bible-tui.cpp: -------------------------------------------------------------------------------- 1 | #include "bible-tui.hpp" 2 | 3 | BibleTUI::BibleTUI(const start_def& defs) : GetVerse(defs.lang, defs.book){ 4 | m_defs = std::make_unique(); 5 | 6 | m_defs->lang = defs.lang; 7 | m_defs->unicode = defs.unicode; 8 | m_defs->theme = defs.theme; 9 | m_defs->book = defs.book; 10 | 11 | set_unicodes(); 12 | set_theme(); 13 | this->run_verse(); 14 | } 15 | 16 | size_t BibleTUI::utf8_length(const std::string& str){ 17 | size_t len = 0; 18 | for(char c : str){ 19 | if((static_cast(c) & 0b11000000) != 0b10000000){ 20 | ++len; 21 | } 22 | } 23 | return len; 24 | } 25 | 26 | std::vector BibleTUI::wrap_text(const std::string& text, int width){ 27 | std::vector lines; 28 | size_t start = 0; 29 | 30 | while(start < text.size()){ 31 | size_t end = start + static_cast(width); 32 | if(end >= text.size()){ 33 | lines.push_back(text.substr(start)); 34 | break; 35 | } 36 | 37 | size_t space_pos = text.rfind(' ', end); 38 | if(space_pos == std::string::npos || space_pos < start){ 39 | space_pos = end; 40 | } 41 | 42 | lines.push_back(text.substr(start, space_pos - start)); 43 | start = (text[space_pos] == ' ') ? space_pos + 1 : space_pos; 44 | } 45 | 46 | return lines; 47 | } 48 | 49 | std::string BibleTUI::pad_line(const std::string& line, int width, 50 | const std::string& content_color){ 51 | size_t visual_len = utf8_length(line); 52 | std::string padding(static_cast(width) - visual_len + TEXT_OFFSET, ' '); 53 | return " " + theme[0] + unicode[3] + " " + content_color + line + padding + theme[0] + " " + unicode[3]; 54 | } 55 | 56 | std::string BibleTUI::make_border(const std::string& left, const std::string& fill, 57 | const std::string& right, int count){ 58 | std::string result = " " + left; 59 | for (int i = 0; i < count; ++i){ 60 | result += fill; 61 | } 62 | result += right; 63 | return result; 64 | } 65 | 66 | void BibleTUI::run(){ 67 | 68 | auto wrapped = wrap_text(verse, MAX_WIDTH); 69 | int border_fill_count = MAX_WIDTH + 6; 70 | 71 | std::string top_border = make_border(unicode[0], unicode[1], unicode[2], border_fill_count); 72 | std::string bottom_border = make_border(unicode[4], unicode[1], unicode[5], border_fill_count); 73 | std::string empty_line = pad_line("", MAX_WIDTH, theme[0]); 74 | 75 | std::cout.put('\n'); // START 76 | std::cout << theme[0]; 77 | 78 | std::cout << top_border << '\n'; 79 | std::cout << empty_line << '\n'; 80 | 81 | for (const auto& line : wrapped) 82 | std::cout << pad_line(line, MAX_WIDTH, theme[1]) << '\n'; 83 | 84 | 85 | std::cout << empty_line << '\n'; 86 | 87 | std::cout << pad_line("— " + capt, MAX_WIDTH, theme[2]) << '\n'; 88 | std::cout << empty_line << '\n'; 89 | std::cout << bottom_border << '\n'; 90 | 91 | std::cout << "\033[m"; 92 | std::cout.put('\n'); // OK 93 | } 94 | 95 | void BibleTUI::set_unicodes(){ 96 | unicodes = {{ 97 | {"┌", "─", "┐", "│", "└", "┘"}, // DEFAULT 98 | {"╔", "═", "╗", "║", "╚", "╝"}, // DOUBLE 99 | {"╭", "─", "╮", "│", "╰", "╯"}, // ROUNDED 100 | {" ", " ", " ", " ", " ", " "} // EMPTY 101 | }}; 102 | 103 | if(m_defs->unicode == MOLD_INVALID) m_defs->unicode = ROUNDED; 104 | unicode = unicodes[m_defs->unicode]; 105 | } 106 | 107 | void BibleTUI::set_theme(){ 108 | themes = {{ 109 | {"\033[38;2;200;200;68m", "\033[1;38;2;166;226;46m", "\033[3;38;2;117;113;94m"}, // real 110 | {"\033[38;2;90;90;90m", "\033[1;38;2;240;83;137m", "\033[3;38;2;158;95;212m"}, // lolcat 111 | {"\033[38;2;255;146;41m", "\033[1;38;2;169;177;189m", "\033[3;38;2;208;153;102m"}, // atom 112 | }}; 113 | if(m_defs->theme == THEME_INVALID) m_defs->theme = LOLCAT; 114 | theme = themes[m_defs->theme]; 115 | } 116 | -------------------------------------------------------------------------------- /src/getverse.cpp: -------------------------------------------------------------------------------- 1 | #include "getverse.hpp" 2 | 3 | GetVerse::GetVerse(const std::string& lang, const std::string& book) : lang(lang), book(book){ 4 | en = "https://bible-api.com/"; 5 | br = "?translation=almeida"; 6 | } 7 | 8 | std::string GetVerse::format(const std::string& str){ 9 | size_t start = 0; 10 | if (str.compare(0, 3, "pt+") == 0) start = 3; 11 | 12 | std::string ret; 13 | ret.reserve(str.size() - start); 14 | 15 | for(size_t i = start; i < str.size(); ++i){ 16 | char c = str[i]; 17 | if(c == '/'){ 18 | ret += ':'; 19 | }else{ 20 | ret += c; 21 | } 22 | } 23 | 24 | return ret; 25 | } 26 | 27 | std::string GetVerse::format_book(const std::string& str){ 28 | size_t start = 0; 29 | if (str.compare(0, 3, "pt+") == 0) start = 3; 30 | 31 | std::string ret; 32 | ret.reserve(str.size() - start); 33 | 34 | for(size_t i = start; i < str.size(); ++i){ 35 | char c = str[i]; 36 | if(c == ':'){ 37 | ret += '+'; 38 | }else{ 39 | ret += c; 40 | } 41 | } 42 | 43 | std::string ret2 = {}; 44 | for(size_t i = start; i < str.size(); ++i){ 45 | char c = ret[i]; 46 | if(c == '-'){ 47 | ret2 += ':'; 48 | }else{ 49 | ret2 += c; 50 | } 51 | } 52 | 53 | return ret2; 54 | } 55 | 56 | void GetVerse::run_verse(){ 57 | try { 58 | curlpp::Cleanup cleanup; 59 | std::ostringstream os; 60 | 61 | curlpp::Easy request1; 62 | 63 | if(lang == "en"){ 64 | request1.setOpt(curlpp::options::Url(en_random)); 65 | }else{ 66 | request1.setOpt(curlpp::options::Url(br_random)); 67 | } 68 | request1.setOpt(curlpp::options::WriteStream(&os)); 69 | request1.perform(); 70 | 71 | std::string html = os.str(); 72 | 73 | std::string search = "
> json; 126 | 127 | if(json["reference"].empty()){ 128 | std::cout << "Try again.\n"; 129 | std::exit(1); 130 | } 131 | 132 | this->verse = json["text"].get(); 133 | this->capt = json["reference"].get(); 134 | 135 | std::replace(verse.begin(), verse.end(), '\n', ' '); 136 | std::replace(verse.begin(), verse.end(), '\r', ' '); 137 | 138 | } catch (curlpp::RuntimeError &e) { 139 | std::cerr << "Erro runtime: " << e.what() << "\n"; 140 | } catch (curlpp::LogicError &e) { 141 | std::cerr << "Erro lógica: " << e.what() << "\n"; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/options.cpp: -------------------------------------------------------------------------------- 1 | #include "options.hpp" 2 | 3 | Options::Options(){} 4 | 5 | THEMES Options::parse_theme(const std::string& value){ 6 | static const std::unordered_map theme_map = { 7 | {"real", REAL}, 8 | {"lolcat", LOLCAT}, 9 | {"atom", ATOM}, 10 | {"style", STYLE} 11 | }; 12 | auto it = theme_map.find(value); 13 | return it != theme_map.end() ? it->second : THEME_INVALID; 14 | } 15 | 16 | UNICODE Options::parse_mold(const std::string& value){ 17 | static const std::unordered_map mold_map = { 18 | {"default", DEFAULT}, 19 | {"double", DOUBLE}, 20 | {"rounded", ROUNDED}, 21 | {"empty", EMPTY} 22 | }; 23 | auto it = mold_map.find(value); 24 | return it != mold_map.end() ? it->second : MOLD_INVALID; 25 | } 26 | 27 | bool Options::validate_book_format(const std::string& book){ 28 | auto colon = book.find(':'); 29 | auto dash = book.find('-'); 30 | if(colon == std::string::npos || dash == std::string::npos || dash <= colon + 1) return false; 31 | 32 | std::string nome = book.substr(0, colon); 33 | std::string num1 = book.substr(colon + 1, dash - colon - 1); 34 | std::string num2 = book.substr(dash + 1); 35 | 36 | if(nome.empty() || num1.empty() || num2.empty()) return false; 37 | 38 | for(char c : nome){ 39 | if (!std::isalpha(c)) return false; 40 | } 41 | 42 | for(char c : num1 + num2){ 43 | if (!std::isdigit(c)) return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | void Options::run_options(int argc, char** argv){ 50 | std::string locale = std::getenv("LANG"); 51 | std::string lang = locale.substr(0, 2); 52 | THEMES theme = THEME_INVALID; 53 | UNICODE mold = MOLD_INVALID; 54 | std::string book; 55 | 56 | for(int i = 1; i < argc; ++i){ 57 | std::string arg = argv[i]; 58 | 59 | if(arg.rfind("--lang=", 0) == 0){ 60 | lang = arg.substr(7); 61 | if(lang != "en" && lang != "pt"){ 62 | std::cerr << "Invalid language: " << lang << "\n"; 63 | std::exit(1); 64 | } 65 | }else if (arg.rfind("--theme=", 0) == 0){ 66 | std::string value = arg.substr(8); 67 | theme = parse_theme(value); 68 | if(theme == THEME_INVALID){ 69 | std::cerr << "Invalid theme: " << value << "\n"; 70 | std::exit(1); 71 | } 72 | }else if (arg.rfind("--mold=", 0) == 0){ 73 | std::string value = arg.substr(7); 74 | mold = parse_mold(value); 75 | if (mold == MOLD_INVALID) { 76 | std::cerr << "Invalid mold: " << value << "\n"; 77 | std::exit(1); 78 | } 79 | }else if(arg.rfind("--book=", 0) == 0){ 80 | std::string value = arg.substr(7); 81 | if (!validate_book_format(value)) { 82 | std::cerr << "Invalid format for --book. Use book:number-number, ex.: psalms:23-1\n"; 83 | std::exit(1); 84 | } 85 | book = value; 86 | }else if(arg == "--help" || arg == "-h"){ 87 | help(); 88 | std::exit(0); 89 | }else if(arg == "--version" || arg == "-v"){ 90 | std::cout << VERSION << '\n'; 91 | std::exit(0); 92 | }else { 93 | std::cerr << "Unrecognized argument: " << arg << "\n"; 94 | std::exit(1); 95 | } 96 | } 97 | 98 | start_def defs = {lang, theme, mold, book}; 99 | 100 | auto bible = std::make_unique(defs); 101 | bible->run(); 102 | } 103 | 104 | void Options::help() { 105 | std::cout << "Usage:\n" 106 | << "bible-tui [options]\n\n" 107 | << "Options:\n" 108 | << " --lang=pt|en Set the language.\n" 109 | << " --theme=real|lolcat|atom|style Set the color theme.\n" 110 | << " --mold=default|double|rounded|empty Set the border style.\n" 111 | << " --book=name:num-num Set the book and verse.\n" 112 | << " Examples:\n" 113 | << " [EN]psalms:23-1 | [PT]salmos:23-1\n" 114 | << " --help, -h Show this message.\n" 115 | << " --version, -v Show version info.\n"; 116 | } 117 | --------------------------------------------------------------------------------