UNPKG

geoshell

Version:

A CLI to fetch real-time geo-data from your terminal

437 lines (370 loc) 12.1 kB
/** * GeoShell CLI Implementation * Command-line interface for GeoShell geo-data library */ const https = require("https") const fs = require("fs") const path = require("path") // Configuration const BASE_URLS = { countries: "https://restcountries.com/v3.1", weather: "https://api.openweathermap.org/data/2.5", holidays: "https://date.nager.at/api/v3", } // Utility functions function makeRequest(url) { return new Promise((resolve, reject) => { https .get(url, (res) => { let data = "" res.on("data", (chunk) => { data += chunk }) res.on("end", () => { try { const jsonData = JSON.parse(data) resolve(jsonData) } catch (error) { reject(new Error("Invalid JSON response")) } }) }) .on("error", (error) => { reject(error) }) }) } function formatOutput(data, format = "json") { switch (format.toLowerCase()) { case "table": return formatAsTable(data) case "csv": return formatAsCSV(data) case "json": default: return JSON.stringify(data, null, 2) } } function formatAsTable(data) { if (Array.isArray(data)) { if (data.length === 0) return "No data found" const headers = Object.keys(data[0]) let table = headers.join("\t") + "\n" table += headers.map(() => "---").join("\t") + "\n" data.forEach((row) => { table += headers.map((header) => row[header] || "").join("\t") + "\n" }) return table } else { let table = "Property\tValue\n" table += "---\t---\n" Object.entries(data).forEach(([key, value]) => { const displayValue = Array.isArray(value) ? value.join(", ") : value table += `${key}\t${displayValue}\n` }) return table } } function formatAsCSV(data) { if (Array.isArray(data)) { if (data.length === 0) return "" const headers = Object.keys(data[0]) let csv = headers.join(",") + "\n" data.forEach((row) => { csv += headers .map((header) => { const value = row[header] || "" return typeof value === "string" && value.includes(",") ? `"${value}"` : value }) .join(",") + "\n" }) return csv } else { let csv = "Property,Value\n" Object.entries(data).forEach(([key, value]) => { const displayValue = Array.isArray(value) ? value.join("; ") : value csv += `${key},"${displayValue}"\n` }) return csv } } // Command implementations async function getCountryInfo(countryName, options = {}) { try { const url = `${BASE_URLS.countries}/name/${encodeURIComponent(countryName)}` const data = await makeRequest(url) if (!data || data.length === 0) { throw new Error(`Country '${countryName}' not found`) } const country = data[0] const countryInfo = { name: country.name?.common || countryName, capital: country.capital?.[0] || "Unknown", population: country.population || 0, area: country.area || 0, currency: Object.keys(country.currencies || {})[0] || "Unknown", languages: Object.values(country.languages || {}).join(", ") || "Unknown", region: country.region || "Unknown", subregion: country.subregion || "Unknown", borders: country.borders?.join(", ") || "None", flag: country.flags?.png || "", } // Filter fields if specified if (options.fields) { const filteredInfo = {} options.fields.forEach((field) => { if (countryInfo[field] !== undefined) { filteredInfo[field] = countryInfo[field] } }) return filteredInfo } return countryInfo } catch (error) { throw new Error(`Failed to fetch country data: ${error.message}`) } } async function getWeatherInfo(location, options = {}) { // Mock weather data for demo purposes const mockWeather = { location: location, temperature: Math.round(Math.random() * 30 + 5), // 5-35°C condition: ["Sunny", "Cloudy", "Partly Cloudy", "Rainy", "Clear"][Math.floor(Math.random() * 5)], humidity: Math.round(Math.random() * 40 + 40), // 40-80% windSpeed: Math.round(Math.random() * 20 + 5), // 5-25 km/h pressure: Math.round(Math.random() * 50 + 1000), // 1000-1050 hPa visibility: Math.round(Math.random() * 10 + 5), // 5-15 km uvIndex: Math.round(Math.random() * 10), // 0-10 } if (options.forecast && options.forecast > 0) { const forecast = { location: location, current: mockWeather, forecast: [], } for (let i = 1; i <= Math.min(options.forecast, 7); i++) { const date = new Date() date.setDate(date.getDate() + i) forecast.forecast.push({ date: date.toISOString().split("T")[0], high: Math.round(Math.random() * 30 + 10), low: Math.round(Math.random() * 15 + 0), condition: ["Sunny", "Cloudy", "Partly Cloudy", "Rainy", "Clear"][Math.floor(Math.random() * 5)], }) } return forecast } return mockWeather } async function getHolidays(country, options = {}) { try { const year = options.year || new Date().getFullYear() const countryCode = getCountryCode(country) const url = `${BASE_URLS.holidays}/PublicHolidays/${year}/${countryCode}` const data = await makeRequest(url) const holidays = data.map((holiday) => ({ name: holiday.name, date: holiday.date, type: holiday.type || "National", })) if (options.upcoming) { const today = new Date().toISOString().split("T")[0] return holidays.filter((holiday) => holiday.date >= today) } return holidays } catch (error) { // Return mock data if API fails return [ { name: "New Year's Day", date: `${options.year || new Date().getFullYear()}-01-01`, type: "National" }, { name: "Independence Day", date: `${options.year || new Date().getFullYear()}-07-04`, type: "National" }, { name: "Christmas Day", date: `${options.year || new Date().getFullYear()}-12-25`, type: "National" }, ] } } async function getNeighbors(country) { try { const countryInfo = await getCountryInfo(country) if (!countryInfo.borders || countryInfo.borders === "None") { return [] } // In a real implementation, we would resolve border codes to country names // For demo purposes, return mock neighbors based on known countries const mockNeighbors = { Germany: [ "Austria", "Belgium", "Czech Republic", "Denmark", "France", "Luxembourg", "Netherlands", "Poland", "Switzerland", ], France: ["Germany", "Belgium", "Luxembourg", "Switzerland", "Italy", "Spain", "Andorra", "Monaco"], Brazil: [ "Argentina", "Bolivia", "Colombia", "French Guiana", "Guyana", "Paraguay", "Peru", "Suriname", "Uruguay", "Venezuela", ], Canada: ["United States"], "United States": ["Canada", "Mexico"], } return mockNeighbors[countryInfo.name] || [] } catch (error) { throw new Error(`Failed to fetch neighbors: ${error.message}`) } } function getCountryCode(country) { const countryCodes = { usa: "US", "united states": "US", canada: "CA", germany: "DE", france: "FR", japan: "JP", brazil: "BR", uk: "GB", "united kingdom": "GB", italy: "IT", spain: "ES", australia: "AU", india: "IN", china: "CN", } return countryCodes[country.toLowerCase()] || country.toUpperCase().substring(0, 2) } // CLI argument parsing function parseArgs(args) { const parsed = { command: "", input: "", options: {}, } if (args.length < 2) { return parsed } parsed.command = args[0] parsed.input = args[1] // Parse options for (let i = 2; i < args.length; i++) { const arg = args[i] if (arg.startsWith("--")) { const option = arg.substring(2) if (i + 1 < args.length && !args[i + 1].startsWith("--")) { parsed.options[option] = args[i + 1] i++ // Skip next argument as it's the value } else { parsed.options[option] = true } } } return parsed } // Main CLI function async function main() { const args = process.argv.slice(2) if (args.length === 0 || args.includes("--help") || args.includes("-h")) { console.log(` GeoShell CLI - Real-time geo-data from your terminal Usage: geoshell <command> <input> [options] Commands: country <name> Get country information weather <city> Get weather data holidays <country> Get national holidays neighbors <country> Get neighboring countries Options: --format <type> Output format: json, table, csv (default: json) --output <file> Save output to file --fields <list> Comma-separated list of fields to include --year <year> Year for holidays (default: current year) --forecast <days> Number of forecast days for weather (1-7) --upcoming Show only upcoming holidays --verbose Enable verbose output --help, -h Show this help message Examples: geoshell country Japan geoshell weather "New York" --forecast 5 geoshell holidays USA --year 2024 geoshell neighbors Germany --format table `) return } if (args.includes("--version") || args.includes("-v")) { console.log("GeoShell CLI v1.0.0") return } const parsed = parseArgs(args) if (!parsed.command || !parsed.input) { console.error("Error: Command and input are required") console.error("Use --help for usage information") process.exit(1) } try { let result switch (parsed.command.toLowerCase()) { case "country": const fields = parsed.options.fields ? parsed.options.fields.split(",") : null result = await getCountryInfo(parsed.input, { fields }) break case "weather": const forecast = parsed.options.forecast ? Number.parseInt(parsed.options.forecast) : 0 result = await getWeatherInfo(parsed.input, { forecast }) break case "holidays": const year = parsed.options.year ? Number.parseInt(parsed.options.year) : null const upcoming = parsed.options.upcoming || false result = await getHolidays(parsed.input, { year, upcoming }) break case "neighbors": result = await getNeighbors(parsed.input) break default: throw new Error(`Unknown command: ${parsed.command}`) } const format = parsed.options.format || "json" const output = formatOutput(result, format) if (parsed.options.output) { fs.writeFileSync(parsed.options.output, output) console.log(`Output saved to ${parsed.options.output}`) } else { console.log(output) } } catch (error) { console.error(`Error: ${error.message}`) if (parsed.options.verbose) { console.error(error.stack) } process.exit(1) } } // Export for testing if (typeof module !== "undefined" && module.exports) { module.exports = { getCountryInfo, getWeatherInfo, getHolidays, getNeighbors, formatOutput, parseArgs, } } // Run CLI if called directly if (require.main === module) { main().catch((error) => { console.error("Unexpected error:", error.message) process.exit(1) }) } console.log("GeoShell CLI implementation loaded successfully!") console.log("Usage: node geoshell_cli.js <command> <input> [options]") console.log("Example: node geoshell_cli.js country Japan --format table")