Skip to main content

Import System

Utah's import system enables modular programming by allowing you to split code across multiple files and reuse functionality.

Basic Import Syntax​

Simple Import​

import "utils.shx";

// Now you can use functions from utils.shx
let result: string = formatOutput("Hello World");

File Organization​

Project Structure​

project/
├── main.shx # Entry point
├── lib/
│ ├── utils.shx # Utility functions
│ ├── config.shx # Configuration handling
│ └── logging.shx # Logging functionality
└── modules/
├── database.shx # Database operations
└── api.shx # API interactions

Import Paths​

Utah resolves imports relative to the current file:

// From main.shx
import "lib/utils.shx";
import "modules/database.shx";

// From lib/config.shx
import "utils.shx"; // Same directory
import "../modules/api.shx"; // Parent directory navigation

Creating Reusable Modules​

Utility Module (lib/utils.shx)​

// lib/utils.shx
script.description("Utility functions for the application");

function formatTimestamp(): string {
return timer.current();
}

function validateEmail(email: string): boolean {
return string.contains(email, "@") && string.contains(email, ".");
}

function ensureDirectory(path: string): void {
if (!fs.exists(path)) {
$(mkdir -p ${path});
console.log("Created directory: ${path}");
}
}

function logMessage(level: string, message: string): void {
let timestamp: string = formatTimestamp();
console.log("[${timestamp}] ${level}: ${message}");
}

Configuration Module (lib/config.shx)​

// lib/config.shx
script.description("Configuration management");

const DEFAULT_CONFIG: string = "{\"database\": {\"host\": \"localhost\", \"port\": 5432}, \"logging\": {\"level\": \"info\"}}";

function loadConfig(): object {
let configFile: string = "config.json";

if (fs.exists(configFile)) {
try {
let content: string = fs.readFile(configFile);
return json.parse(content);
}
catch {
console.log("Invalid config file, using defaults");
}
}

return json.parse(DEFAULT_CONFIG);
}

function getConfigValue(config: object, key: string): string {
return json.get(config, key);
}

Database Module (modules/database.shx)​

// modules/database.shx
import "../lib/utils.shx";
import "../lib/config.shx";

script.description("Database connection and operations");

function connectToDatabase(): object {
let config: object = loadConfig();
let host: string = getConfigValue(config, ".database.host");
let port: string = getConfigValue(config, ".database.port");

logMessage("INFO", "Connecting to database at ${host}:${port}");

// Database connection logic here
return { host: host, port: port, connected: true };
}

function executeQuery(connection: object, query: string): string {
logMessage("DEBUG", "Executing query: ${query}");

// Query execution logic
return $(psql -h ${connection.host} -p ${connection.port} -c \"${query}\");
}

Using Imported Modules​

Main Application (main.shx)​

// main.shx
import "lib/utils.shx";
import "lib/config.shx";
import "modules/database.shx";

script.description("Main application with modular architecture");

function main(): void {
logMessage("INFO", "Application starting");

// Load configuration
let config: object = loadConfig();

// Ensure required directories
ensureDirectory("/var/log/myapp");
ensureDirectory("/tmp/myapp");

// Connect to database
try {
let db: object = connectToDatabase();

// Execute some queries
let result: string = executeQuery(db, "SELECT version()");
logMessage("INFO", "Database version: ${result}");
}
catch (error) {
logMessage("ERROR", "Database connection failed: ${error}");
exit(1);
}

logMessage("INFO", "Application completed successfully");
}

main();

Advanced Import Patterns​

Conditional Imports​

// Load different modules based on environment
let environment: string = env.get("APP_ENV", "development");

if (environment == "production") {
import "modules/prod-logging.shx";
}
else {
import "modules/dev-logging.shx";
}

Dynamic Import Resolution​

// Import based on configuration
let config: object = loadConfig();
let databaseType: string = getConfigValue(config, ".database.type");

if (databaseType == "postgresql") {
import "drivers/postgresql.shx";
}
else if (databaseType == "mysql") {
import "drivers/mysql.shx";
}
else {
console.log("Unsupported database type");
exit(1);
}

Module Best Practices​

Function Organization​

Keep related functions together in logical modules:

// math/calculations.shx
function add(a: number, b: number): number {
return a + b;
}

function multiply(a: number, b: number): number {
return a * b;
}

function calculatePercentage(value: number, total: number): number {
return (value / total) * 100;
}

Constants and Configuration​

Define module-level constants:

// constants/app.shx
const APP_NAME: string = "MyApplication";
const VERSION: string = "1.0.0";
const DEFAULT_TIMEOUT: number = 30;

function getAppInfo(): string {
return "${APP_NAME} v${VERSION}";
}

Error Handling in Modules​

Consistent error handling across modules:

// lib/errors.shx
function handleError(module: string, operation: string, error: string): void {
let timestamp: string = timer.current();
let message: string = "[${timestamp}] ${module}.${operation}: ${error}";

console.log(message);
fs.appendFile("/var/log/app-errors.log", "${message}\n");
}

// Usage in other modules
function riskyOperation(): string {
try {
return `$(some-command)`;
}
catch (error) {
handleError("database", "connect", error);
throw "Database connection failed";
}
}

Testing Modules​

Unit Testing Approach​

// tests/test-utils.shx
import "../lib/utils.shx";

function testValidateEmail(): void {
// Test valid email
if (!validateEmail("user@example.com")) {
console.log("FAIL: Valid email rejected");
exit(1);
}

// Test invalid email
if (validateEmail("invalid-email")) {
console.log("FAIL: Invalid email accepted");
exit(1);
}

console.log("PASS: Email validation tests");
}

function runTests(): void {
console.log("Running utility tests...");
testValidateEmail();
console.log("All tests passed!");
}

runTests();

Dependency Management​

Explicit Dependencies​

Document module dependencies clearly:

// modules/reporting.shx
// Dependencies:
// - lib/utils.shx (for logging and formatting)
// - lib/config.shx (for report configuration)
// - modules/database.shx (for data access)

import "../lib/utils.shx";
import "../lib/config.shx";
import "database.shx";

function generateReport(): void {
logMessage("INFO", "Starting report generation");

let config: object = loadConfig();
let db: object = connectToDatabase();

// Report generation logic
}

Circular Dependency Prevention​

Avoid circular imports by organizing dependencies hierarchically:

lib/          # Low-level utilities (no imports)
├── utils.shx
├── config.shx
└── logging.shx

modules/ # Business logic (imports from lib/)
├── database.shx
├── api.shx
└── reporting.shx

main.shx # Entry point (imports from modules/ and lib/)

Performance Considerations​

Import Optimization​

  • Import only what you need
  • Place imports at the top of files
  • Avoid deep import chains when possible
// Good: Focused imports
import "lib/logging.shx"; // Only need logging

// Less optimal: Importing everything
import "lib/all-utilities.shx"; // Imports many unused functions

Module Caching​

Utah processes each imported file once per script execution, so modules are naturally cached.

Common Import Patterns​

Library Structure​

// lib/index.shx - Main library entry point
import "utils.shx";
import "config.shx";
import "logging.shx";

// Applications can then import the entire library
import "lib/index.shx";

Feature Modules​

// features/user-management.shx
import "../lib/database.shx";
import "../lib/validation.shx";

function createUser(username: string, email: string): boolean {
if (!validateEmail(email)) {
return false;
}

// User creation logic
return true;
}

Service Layers​

// services/notification.shx
import "../lib/config.shx";

function sendEmail(to: string, subject: string, body: string): boolean {
let config: object = loadConfig();
let smtpHost: string = getConfigValue(config, ".smtp.host");

// Email sending logic
return true;
}

The import system in Utah promotes code reusability, maintainability, and clear separation of concerns, making it easier to build complex shell automation tools.