initial import

0.10
alex 2 years ago
commit 0a505782aa

@ -0,0 +1,21 @@
The MIT License (Expat)
Copyright (c) 2015-2022, Zig contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,46 @@
## [zig](https://ziglang.org) docs on command line.
usage:
zdoc [source] <identifier>
the program searches source code for matching public identifiers,
printing found types and their doc comments to stdout.
the search is case-insensitive and non-exhaustive.
for example, look up format function in std lib:
zdoc std.fmt format
print fmt's top level doc comments:
zdoc std.fmt
look up "hello" identifier in a project file:
zdoc ./src/main.zig hello
search across all .zig files starting from the src directory,
recursively and following symlinks:
zdoc ./src hello
---
that's about all it can do for now. a future version may include search
syntax for public struct fields, enum tags and other constructs, like so:
zdoc std.foo.bar .somefield
---
to contribute, create a pull request or send a patch with
[git send-mail](https://git-scm.com/docs/git-send-email) to alex-dot-cloudware.io.
before sending a change, please make sure all code is formatted. check with:
zig fmt --check .
### license
same as zig license.

@ -0,0 +1,23 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const target = b.standardTargetOptions(.{});
const bmode = b.standardReleaseOptions();
const do_strip = b.option(bool, "strip", "strip output; on for release-small") orelse (bmode == .ReleaseSmall);
const exe = b.addExecutable("zdoc", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(bmode);
exe.strip = do_strip;
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "run the executable");
run_step.dependOn(&run_cmd.step);
}

@ -0,0 +1,101 @@
const std = @import("std");
const output = @import("output.zig");
const Ast = std.zig.Ast;
/// search parses the source code into std.zig.Ast and walks over top level declarations,
/// optionally matching against the query.
///
/// results are printed using ais.
pub fn search(alloc: std.mem.Allocator, ais: *output.Ais, source: [:0]const u8, query: ?[]const u8) !void {
const tree = try std.zig.parse(alloc, source);
for (tree.rootDecls()) |decl| {
if (!isPublic(tree, decl)) {
continue;
}
if (query != null and !identifierMatch(tree, decl, query.?)) {
continue;
}
try output.renderPubMember(alloc, ais, tree, decl, .newline);
try ais.insertNewline();
}
}
/// reports whether the declaration is visible to other modules.
pub fn isPublic(tree: Ast, decl: Ast.Node.Index) bool {
const token_tags = tree.tokens.items(.tag);
var i = tree.nodes.items(.main_token)[decl];
while (i > 0) {
i -= 1;
switch (token_tags[i]) {
.keyword_export,
.keyword_pub,
=> return true,
.keyword_extern,
.keyword_comptime,
.keyword_threadlocal,
.keyword_inline,
.keyword_noinline,
.string_literal,
=> continue,
else => break,
}
}
return false;
}
/// reports whether the given name matches decl identifier, case-insensitive.
pub fn identifierMatch(tree: Ast, decl: Ast.Node.Index, name: []const u8) bool {
return if (identifier(tree, decl)) |ident| std.ascii.eqlIgnoreCase(name, ident) else false;
//std.debug.print("identifierMatch? name={s} ", .{name});
//if (identifier(tree, decl)) |ident| {
// const res = std.ascii.eqlIgnoreCase(name, ident);
// std.debug.print("ident={s} res={}\n", .{ident, res});
// return res;
//} else {
// std.debug.print("ident is null\n", .{});
// return false;
//}
}
/// identifier returns node's identifier, if any.
fn identifier(tree: Ast, node: Ast.Node.Index) ?[]const u8 {
switch (tree.nodes.items(.tag)[node]) {
.fn_decl => return identifier(tree, tree.nodes.items(.data)[node].lhs),
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
=> |tag| {
const proto = switch (tag) {
.fn_proto_simple => simple: {
var params: [1]Ast.Node.Index = undefined;
const proto = tree.fnProtoSimple(&params, node);
break :simple proto;
},
.fn_proto_one => one: {
var params: [1]Ast.Node.Index = undefined;
const proto = tree.fnProtoOne(&params, node);
break :one proto;
},
.fn_proto_multi => tree.fnProtoMulti(node),
.fn_proto => tree.fnProto(node),
else => unreachable,
};
const idx = proto.ast.fn_token + 1;
if (tree.tokens.items(.tag)[idx] == .identifier) {
return tree.tokenSlice(idx);
}
},
.simple_var_decl => {
const decl = tree.simpleVarDecl(node);
return tree.tokenSlice(decl.ast.mut_token + 1);
},
else => return null,
}
return null;
}

@ -0,0 +1,178 @@
//! zdoc searches source code according to the provided query and prints
//! the results to stdout. see usage for details.
const std = @import("std");
const analyze = @import("analyze.zig");
const output = @import("output.zig");
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
// parse cmd line args
var args = try std.process.ArgIterator.initWithAllocator(alloc);
defer args.deinit();
const progname = args.next().?;
var zquery: ?[:0]const u8 = null;
var zsource: [:0]const u8 = undefined;
var nargs: u8 = 0;
while (args.next()) |a| {
switch (nargs) {
0 => {
zsource = a;
nargs += 1;
},
1 => {
zquery = a;
nargs += 1;
},
else => fatal("too many args", .{}),
}
}
if (nargs == 0) { // expected 1 or 2 args
usage(progname) catch {};
return;
}
// output all results to stdout
var auto_indenting_stream = output.Ais{
.indent_delta = 4,
.underlying_writer = stdout,
};
const ais = &auto_indenting_stream;
// run the search, one file at a time
var query: ?[]const u8 = if (zquery) |q| q[0..] else null;
const list = try expandSourcePath(alloc, zsource);
for (list.items) |src| {
// todo: consider replacing arena with something else to dealloc already
// analyzed files early and possibly reduce memory footprint.
const contents = try readFile(alloc, src);
try analyze.search(alloc, ais, contents, query);
}
}
fn usage(prog: []const u8) !void {
try stderr.print(
\\usage: {s} [source] <identifier>
\\
\\the program searches source code for matching public identifiers,
\\printing found types and their doc comments to stdout.
\\the search is case-insensitive and non-exhaustive.
\\
\\for example, look up format function in std lib:
\\
\\ zdoc std.fmt format
\\
\\print fmt's top level doc comments:
\\
\\ zdoc std.fmt
\\
\\look up "hello" identifier in a project file:
\\
\\ zdoc ./src/main.zig hello
\\
\\search across all .zig files starting from the src directory,
\\recursively and following symlinks:
\\
\\ zdoc ./src hello
\\
, .{prog});
}
/// fatal prints an std.format-formatted message and terminates the program
/// with exit code 1.
fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
stderr.print(fmt, args) catch {};
if (fmt[fmt.len - 1] != '\n') {
stderr.writeByte('\n') catch {};
}
std.os.exit(1);
}
/// todo: caller must free ...
fn readFile(alloc: std.mem.Allocator, name: []const u8) ![:0]const u8 {
var b: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const realpath = try std.fs.realpath(name, &b);
const file = try std.fs.openFileAbsolute(realpath, .{ .mode = .read_only });
defer file.close();
const st = try file.stat();
const contents = try alloc.allocSentinel(u8, st.size, '\x00');
_ = try file.readAll(contents);
return contents;
}
/// todo: caller must free ...
fn expandSourcePath(alloc: std.mem.Allocator, name: []const u8) !std.ArrayList([]const u8) {
// check for special std case
if (std.mem.startsWith(u8, name, "std.")) {
const stdroot = try zigStdPath(alloc);
// std.foo.bar -> std/foo/bar and append .zig extension
const fspath = try std.mem.join(alloc, &.{}, &.{
try std.mem.replaceOwned(u8, alloc, name[4..], ".", std.fs.path.sep_str),
".zig",
});
var list = try std.ArrayList([]const u8).initCapacity(alloc, 1);
try list.append(try std.fs.path.join(alloc, &.{ stdroot, fspath }));
return list;
}
// otherwise, try a fileystem path
var b: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const realpath = try std.fs.realpath(name, &b);
const stat = stat: {
const file = try std.fs.openFileAbsolute(realpath, .{ .mode = .read_only });
defer file.close();
const info = try file.stat();
break :stat info;
};
// simple case, a single file
if (stat.kind == .File) {
var list = try std.ArrayList([]const u8).initCapacity(alloc, 1);
try list.append(name);
return list;
}
// openIterableDir follows symlinks by default.
var root = try std.fs.cwd().openIterableDir(realpath, .{});
defer root.close();
var walker = try root.walk(alloc);
defer walker.deinit();
var list = std.ArrayList([]const u8).init(alloc);
// todo: this walks into zig-cache and .dot dirs; switch to raw root.iterate()
while (try walker.next()) |entry| {
if (entry.kind != .File or !std.mem.eql(u8, ".zig", std.fs.path.extension(entry.path))) {
continue;
}
//std.debug.print("walker entry: {s}\n", .{entry.path});
const srcfile = try std.fs.path.join(alloc, &.{ realpath, entry.path });
try list.append(srcfile);
}
return list;
}
fn zigStdPath(alloc: std.mem.Allocator) ![]const u8 {
const res = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &[_][]const u8{ "zig", "env" },
});
defer {
alloc.free(res.stdout);
alloc.free(res.stderr);
}
if (res.term.Exited != 0) {
fatal("zig env: {s}", .{res.stderr});
}
const Env = struct { std_dir: []const u8 };
const opt = .{ .allocator = alloc, .ignore_unknown_fields = true };
var jenv = try std.json.parse(Env, &std.json.TokenStream.init(res.stdout), opt);
defer std.json.parseFree(Env, jenv, opt);
return alloc.dupe(u8, jenv.std_dir);
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save