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.