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