initial import
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(¶ms, node);
|
||||
break :simple proto;
|
||||
},
|
||||
.fn_proto_one => one: {
|
||||
var params: [1]Ast.Node.Index = undefined;
|
||||
const proto = tree.fnProtoOne(¶ms, 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…
Reference in New Issue