diff options
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..62d3fa4 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,163 @@ +const z = @import("std"); +const urlencode = @import("percent_encoding.zig"); + +var gpa = z.heap.GeneralPurposeAllocator( .{ .thread_safe = true } ){}; +const alloc = gpa.allocator(); + +const JishoData = struct { + pub const DataEntry = struct { + slug: []const u8, + japanese: []struct { + word: []const u8, + reading: []const u8, + }, + senses: []struct { + english_definitions: [][]const u8 + }, + }; + + meta: struct { status: u32 }, + data: []DataEntry, +}; + +/// freed by caller +fn requestWord( word: []const u8 ) ![]const u8 { + var client = z.http.Client{ .allocator = alloc }; + defer client.deinit(); + + const encoded = urlencode.encode_alloc( alloc, word, .{} ) catch |e| { + z.debug.print( "error encoding word {s}\n", .{ word } ); return e; + }; + defer alloc.free( encoded ); + + const url = try z.fmt.allocPrint( alloc, "https://jisho.org/api/v1/search/words?keyword={s}", .{ encoded } ); + defer alloc.free( url ); + const uri = try z.Uri.parse( url ); + + var buf: [4096]u8 = undefined; + var req = try client.open( .GET, uri, .{ .server_header_buffer = &buf } ); + defer req.deinit(); + + req.send() catch |e| { z.debug.print( "error sending request to {s}\n", .{ url } ); return e; }; + req.finish() catch |e| { z.debug.print( "error sending request to {s}\n", .{ url } ); return e; }; + req.wait() catch |e| { z.debug.print( "error sending request to {s}\n", .{ url } ); return e; }; + + if( req.response.status != .ok ) { + z.debug.print( "invalid response from {s}: {d}\n", .{ url, @intFromEnum( req.response.status ) } ); + return error.InvalidResponse; + } + + var reader = req.reader(); + const body = try reader.readAllAlloc( alloc, 999999 ); + + return body; +} + +fn formatDef( buf: []u8, data: *JishoData.DataEntry, definition_count: u32, sense_count: u32 ) ![]const u8 { + if( data.japanese.len == 0 ) { + return error.NotJapanese; + } + + const wordb = try z.fmt.bufPrint( buf, "{s}({s}) - ", .{ data.japanese[0].word, data.japanese[0].reading } ); + var len = wordb.len; + var engb: []const u8 = buf[len..]; + for( data.senses, 0.. ) |sense, i| { + if( i > sense_count ) { + engb = try z.fmt.bufPrint( buf[len..], ", etc...", .{} ); + len += engb.len; + break; + } + + for( sense.english_definitions, 0.. ) |definition, j| { + if( j > definition_count ) + break + else if( j < definition_count and j < sense.english_definitions.len - 1 ) + engb = try z.fmt.bufPrint( buf[len..], "{s}/", .{ definition } ) + else + engb = try z.fmt.bufPrint( buf[len..], "{s}", .{ definition } ); + len += engb.len; + } + + if( i < sense_count and i < data.senses.len - 1 ) { + engb = try z.fmt.bufPrint( buf[len..], ", ", .{} ); + len += engb.len; + } + } + + return buf[0..len]; +} + +fn parseArgs( definitions_count: *u32, senses_count: *u32 ) ![]const u8 { + const args = try z.process.argsAlloc( alloc ); + defer z.process.argsFree( alloc, args ); + if( args.len < 2 ) { + z.debug.print( "usage: {s} [-d <definitions> -s <senses>] <word>\n", .{args[0]} ); + return error.InvalidArgs; + } + + var usedargs: u32 = 0; + for( args, 0.. ) |arg, i| { + if( z.mem.eql( u8, arg, "-d" ) ) { + if( i == args.len - 1 ) + return error.InvalidDefinitionCount; + + definitions_count.* = z.fmt.parseInt( u32, args[i + 1], 10 ) catch return error.InvalidSenseCount; + usedargs += 1; + } + if( z.mem.eql( u8, arg, "-s" ) ) { + if( i == args.len - 1 ) + return error.InvalidSenseCount; + + senses_count.* = z.fmt.parseInt( u32, args[i + 1], 10 ) catch return error.InvalidSenseCount; + usedargs += 1; + } + } + + if( args.len - usedargs < 2 ) { + z.debug.print( "usage: {s} [-d <definitions> -s <senses>] <word>\n", .{args[0]} ); + z.process.argsFree( alloc, args ); + return error.InvalidArgCount; + } + + return alloc.dupe( u8, args[args.len - 1] ); +} + +pub fn main() !void { + var definitions_count: u32 = 3; + var senses_count: u32 = 4; + + const word = parseArgs( &definitions_count, &senses_count ) catch |e| { + z.debug.print( "failed to parse arguments: {any}\n", .{e} ); + return; + }; + const res = requestWord( word ) catch |e| { + z.debug.print( "failed to request word: {any}\n", .{e} ); + return; + }; + const parsed = z.json.parseFromSlice( JishoData, alloc, res, .{ .ignore_unknown_fields = true } ) catch |e| { + z.debug.print( "failed to parse response body ({any}): {s}\n", .{e, res} ); + return; + }; + if( parsed.value.data.len == 0 ) { + z.debug.print( "response empty: not japanese?\n", .{} ); + return; + } + + const data = &parsed.value.data[0]; + + // Good Enough:tm: + var buf: [64000]u8 = undefined; + const str = formatDef( &buf, data, definitions_count - 1, senses_count - 1 ) catch |e| { + if( e == error.NotJapanese ) { + z.debug.print( "response empty: not japanese?\n", .{} ); + return; + } + return e; + }; + try z.io.getStdOut().writer().print( "{s}\n", .{str} ); + + parsed.deinit(); + alloc.free( word ); + alloc.free( res ); + _ = gpa.deinit(); +} |
