wikian 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b0fc9564425d05bbd600b386508385cbbd089f2a9174fcebee5020933aa8e2f
4
- data.tar.gz: d56b498ab07f9fa12a754248560963cf7dfda45874c7540e6840f832bad605df
3
+ metadata.gz: 91aba0c780899759805ebdd5d68297d87ecce58afaac94f99d2314883c760343
4
+ data.tar.gz: 4b586d366edfcddc22db22a4e56255845e2da3545aee038a888b69db123f7dc4
5
5
  SHA512:
6
- metadata.gz: 5eafc016cab2eae0f4ebb8288d41ba5e445562a5d51c9ec923be48a1b332988558ea1874a2f5e7a08a4cd7443e214257f0a1f593b74b8a426373dd26bcfc9895
7
- data.tar.gz: d98f2043ada681f9fc2a88433c7e852908b2af9835efa3da518aa05619b8bb746442c247eda6611991d8209e5833f150b026a578abbcbdc7ebc910272f2d0ada
6
+ metadata.gz: 513a2b007c95ba82da1c701409a3cfe3323f406a97a13215db6edf4ea72693d1f11d43198a0f0c51dd7f82ff9d428f4b91cc21fed3dd46dcd63727a9936304ba
7
+ data.tar.gz: 58d5b4a8873584183b7efe189f56a8f3f762ff0326989cc38610fdc793ad8bb89310b1567536362c039bd0e87f7dc653b614ffe6c5cbd3f807a4d59927a341b6
@@ -0,0 +1,21 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ wikian (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ minitest (5.14.2)
10
+ rake (12.3.3)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ minitest (~> 5.0)
17
+ rake (~> 12.0)
18
+ wikian!
19
+
20
+ BUNDLED WITH
21
+ 2.1.4
data/README.md CHANGED
@@ -1,40 +1,62 @@
1
1
  # Wikian
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/wikian`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Want to be happier while editing wiki files?
4
+ Try [Wikian](https://rubygems.org/gems/wikian):
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
- ## Installation
6
+ ```
7
+ $ gem install wikian
8
+ ```
8
9
 
9
- Add this line to your application's Gemfile:
10
+ To use it, first create a wikipedia account and get a user name and a password.
11
+ Then define and export the `WIKI_USER` and `SECRET_WIKI_PASS` variables in your `.bashrc`:
10
12
 
11
- ```ruby
12
- gem 'wikian'
13
+ ```bash
14
+ export WIKI_USER='Example_user'
15
+ export SECRET_WIKI_PASS='example_password'
13
16
  ```
14
17
 
15
- And then execute:
18
+ Wikian works across [Wikimedia sites](https://meta.wikimedia.org/wiki/Our_projects).
19
+ Create a file following the convention:
16
20
 
17
- $ bundle install
21
+ ```
22
+ <article_name>.<site>.wiki
23
+ ```
18
24
 
19
- Or install it yourself as:
25
+ ## Examples
20
26
 
21
- $ gem install wikian
27
+ To append some text to your Wiktionary user profile:
22
28
 
23
- ## Usage
29
+ ```bash
30
+ $ cat User:$WIKI_USER.en.wikitionary.org.wiki
24
31
 
25
- TODO: Write usage instructions here
32
+ == Last section==
33
+ testing
26
34
 
27
- ## Development
35
+ $ wi post -p User:$WIKI_USER.en.wikitionary.org.wiki
36
+ Article uploaded
37
+ ```
28
38
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
39
+ To edit an article first you need to get its wikitext.
40
+ Generate a template `wiki.yml` config:
30
41
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
42
+ ```
43
+ wi g -t
44
+ ```
32
45
 
33
- ## Contributing
46
+ Then get an article to edit
34
47
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wikian.
48
+ ```bash
49
+ $ wi get https://en.wikipedia.org/wiki/Wikipedia:Sandbox
50
+ Writing to Wikipedia:Sandbox.en.wikipedia.org.json
51
+ Writing to Wikipedia:Sandbox.en.wikipedia.org.wiki
52
+ ```
36
53
 
54
+ Edits the wiki file in Vim, \*ahem\*, in your favorite text editor.
55
+ When ready, uploaded it:
37
56
 
38
- ## License
57
+ ```bash
58
+ $ wi post Wikipedia:Sandbox.en.wikipedia.org.wiki
59
+ Article uploaded
60
+ ```
39
61
 
40
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
62
+ Vim users should try out [mediawiki.vim](https://en.wikipedia.org/wiki/Help:Text_editor_support#Vim), it's awesome.
data/exe/wi ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
+
5
+ require "wikian"
6
+
7
+ Wikian.new(nil).help if ARGV.empty?
8
+
9
+ Wikian.new(*ARGV).run
data/exe/wikian CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
-
5
- require "wikian"
3
+ # fall back to `wi` executable
4
+ system("wi #{ARGV.join(' ')}")
File without changes
@@ -1,8 +1,122 @@
1
1
  $LOAD_PATH.unshift __dir__
2
2
 
3
- require "wikian/version"
3
+ require 'wikian/subcommand'
4
+ require 'wikian/get'
5
+ require 'wikian/post'
6
+ require 'wikian/search'
7
+ require 'wikian/version'
4
8
 
5
- module Wikian
6
- class Error < StandardError; end
7
- # Your code goes here...
9
+ # stdlib
10
+ require 'fileutils'
11
+ require 'json'
12
+ require 'net/http'
13
+ require 'open-uri'
14
+ require 'yaml'
15
+
16
+ # external libraries
17
+ require 'byebug'
18
+
19
+ class Array
20
+ # return true if `self` and `elms` have any element in common
21
+ def have?(elms)
22
+ (self & elms).length > 0 ? true : false
23
+ end
24
+ alias_method :has?, :have?
25
+ end
26
+
27
+ class Hash
28
+ # return a query string representation of a hash
29
+ def to_query
30
+ URI.decode(URI.encode_www_form(self))
31
+ end
32
+ end
33
+
34
+ class Wikian
35
+ class WikianError < StandardError; end
36
+ class UnknownSubcommandError < WikianError; end
37
+
38
+ attr_accessor :subcommand, :args
39
+
40
+ CONFIG_FILE = 'wiki.yml'
41
+ RESPONSE_FORMAT = 'json'
42
+
43
+ def initialize(*args)
44
+ @args = args
45
+ end
46
+
47
+ def run
48
+ if args.have?(%w(-h --help))
49
+ help
50
+ end
51
+
52
+ @subcommand = args.shift
53
+
54
+ raise(UnknownSubcommandError, "Unkown Subcommand") unless %w(g p s get post search).include?(subcommand)
55
+
56
+ if subcommand[0] == 'g'
57
+ api = Wikian::Get.new(args)
58
+ api.doit
59
+ api.extract_wikitext
60
+ elsif subcommand[0] == 's'
61
+ api = Wikian::Search.new(args)
62
+ api.doit
63
+ else
64
+ api = Wikian::Post.new(args)
65
+ api.post
66
+ end
67
+
68
+ rescue UnknownSubcommandError => e
69
+ puts "#{e.class} #{e.message}"
70
+ end
71
+
72
+ def help
73
+ puts <<~eos
74
+ Usage:
75
+ wiki [options] <subcommand> [url|file]
76
+
77
+ Options:
78
+ -a, --append append the input file
79
+ -c, --captcha ID:MESSAGE captcha info
80
+ -d, --debug debug
81
+ -m, --message MESSAGE add a commit message (HIGHLY recommended)
82
+ -p, --prepend prepend the input file
83
+ -r, --remove-cookie remove API cookie
84
+ -t, --template create template configuration file
85
+ -v, --version
86
+
87
+ Subcommands:
88
+ g, get get wikitext file from a wikipedia article
89
+ p, post post wikitext file to a wikipedia article
90
+ s, search search wikitext file to a wikipedia article
91
+
92
+ Examples:
93
+ # create wiki.yml template
94
+ wiki -t
95
+
96
+ # download article and create response and wikitext files
97
+ wiki get https://en.wikipedia.org/wiki/Spider-Man
98
+
99
+ # upload file to English Wikipedia
100
+ wiki post Spider-Man.en.wikipedia.org.wiki
101
+
102
+ # upload file to Spanish Wikipedia
103
+ wiki post Spider-Man.es.wikipedia.org.wiki
104
+
105
+ # upload file to English Wiktionary
106
+ wiki file to Spider-Man.es.wiktionary.org.wiki
107
+
108
+ # append new section to article
109
+ wiki post -a Spider-Man-new-section.wiki
110
+
111
+ # heavy use of the API may require cache validation
112
+ wiki post -c 1234:someMessage spider-Man.wiki
113
+
114
+ Comments:
115
+ Posted files must follow the convention:
116
+ <article_name>.<host>.wiki
117
+ where <host> is a wikimedia site.
118
+ More info at: https://meta.wikimedia.org/wiki/Our_projects
119
+ eos
120
+ exit
121
+ end
8
122
  end
@@ -0,0 +1,96 @@
1
+ class Wikian
2
+ class WikianGetError < StandardError; end
3
+ class ExtractWikiError < WikianGetError; end
4
+ class ArgumentRequiredError < WikianGetError; end
5
+
6
+ class Get < Subcommand
7
+ attr_accessor :title
8
+
9
+ def initialize(args)
10
+ raise ArgumentRequiredError if args.empty?
11
+
12
+ super
13
+
14
+ url = URI(args.find{|arg| arg =~ URI.regexp})
15
+
16
+ @title = File.basename(url.path)
17
+
18
+ @output_file = title + '.' + url.host
19
+
20
+ @params.merge!('titles' => title, 'format' => Wikian::RESPONSE_FORMAT)
21
+
22
+ @query = @params.to_query
23
+
24
+ @api_url = URI("https://#{url.host}/w/api.php?#{query}")
25
+ rescue => e
26
+ puts "#{e.class} #{e.message} in #{__FILE__}"
27
+ exit
28
+ end
29
+
30
+ # extract wikitext from the response file and save it into a `.wiki` file
31
+ #
32
+ # return: nil
33
+ def extract_wikitext
34
+ if !res['content-type'].match?('json') || !(pages = JSON.parse(res.body).dig('query','pages'))
35
+ raise ExtractWikiError, 'JSON response has no pages'
36
+ end
37
+
38
+ create_wiki = -> (title, revisions) do
39
+ revisions.each do |revision|
40
+ wiki_file= File.basename(response_file, File.extname(response_file)) + '.wiki'
41
+ if revision['revid'].nil? && revisions.size > 1
42
+ STDERR.puts "Warning: you should specify 'revid' in #{Wikian::CONFIG_FILE} to prevent overriding different revisions"
43
+ end
44
+ File.open(wiki_file,'w') do |f|
45
+ content = revision.dig('slots', 'main', 'content') ||
46
+ revision.dig('slots', '*') ||
47
+ revision.dig('*')
48
+ STDERR.puts "Warning: nil 'content' in #{Wikian::CONFIG_FILE}" unless content
49
+ STDERR.puts "Writing to #{wiki_file}"
50
+ f.puts content
51
+ end
52
+ end
53
+ end
54
+
55
+ # this is ugly, but Wikipedia is inconsistent in their JSON value for 'pages'. Sometimes it's a hash, sometimes it's an array.
56
+ if pages.respond_to? :keys
57
+ byebug
58
+ create_wiki.call(pages.values.first['title'], pages.values.first['revisions'])
59
+ else
60
+ pages.each do |page|
61
+ create_wiki.call(page['title'], page['revisions'])
62
+ end
63
+ end
64
+
65
+ rescue => e
66
+ puts "An error occurred while extracting the wikitext",
67
+ "Try using a new config file by pasing the '-t' option.",
68
+ "Or pass '-d' option for debugging"
69
+ exit
70
+ end
71
+
72
+ def template
73
+ <<~eos
74
+ meta:
75
+ headers:
76
+ user-agent: Wikian
77
+ api:
78
+ action:
79
+ - query
80
+ prop:
81
+ - revisions
82
+ rvprop:
83
+ - content
84
+ #rvsection:
85
+ # - 0
86
+ # - 2
87
+ rvslots:
88
+ - main
89
+ formatversion:
90
+ - 2
91
+ format:
92
+ - json
93
+ eos
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,140 @@
1
+ class Wikian
2
+ class WikianPostError < StandardError; end
3
+ class WikiFileError < WikianPostError; end
4
+
5
+ class Post
6
+ attr_accessor :args, :baseurl, :header, :input_file, :debug, :login_token,
7
+ :login_cookie, :csrf_token, :csrf_cookie, :query, :body_text, :username
8
+
9
+ def initialize(args)
10
+ @args = args
11
+
12
+ # input wikitext file
13
+ @input_file = args.find{|f| File.exist? f}
14
+ raise WikiFileError unless input_file
15
+
16
+ site = input_file.match(/\.(.*)\.wiki/)[1]
17
+
18
+ @baseurl = "https://#{site}/w/api.php"
19
+
20
+ @header = {}
21
+
22
+ @username = ENV['WIKI_USER']
23
+
24
+ @debug = (args & %w(-d --debug)).length > 0 ? true : false
25
+ rescue => e
26
+ puts "#{e.class} in #{__FILE__}"
27
+ exit
28
+ end
29
+
30
+ def post
31
+ # remove expired cookie
32
+ if expired_cookie? || args.have?(%w(-r --remove-cookie))
33
+ FileUtils.rm_f(csrf_cookie_file)
34
+ end
35
+
36
+ # csrf_cookie can be reused among multiple requests. But csrf_token must be updated on each request
37
+ if File.exist?(csrf_cookie_file)
38
+ @csrf_cookie = File.read(csrf_cookie_file)
39
+ else
40
+ get_login_token
41
+
42
+ get_csrf_cookie
43
+ end
44
+ get_csrf_token
45
+
46
+ build_query_string
47
+
48
+ upload_article
49
+ end
50
+
51
+ # check if the cookie is expired (older than an hour)
52
+ def expired_cookie?
53
+ File.exist?(csrf_cookie_file) && ((Time.now - File.open(csrf_cookie_file).stat.ctime)/3600 > 1)
54
+ end
55
+
56
+ def csrf_cookie_file
57
+ 'csrf_cookie'
58
+ end
59
+
60
+ def remove_cookie_metadata(cookie)
61
+ cookie.gsub(/secure;|path=.*?[,;]|httponly[;,]|samesite=.*?[;,]|expires=.....*?[,;]|domain=.*?;|max-age=.*?[;,]/i,'').squeeze(' ')
62
+ end
63
+
64
+ def get_login_token
65
+ puts("Getting login token/cookie") if debug
66
+ url = URI("#{baseurl}?action=query&meta=tokens&format=json&type=login")
67
+ res = URI.open(url)
68
+ json = JSON.parse(res.read)
69
+ @login_token = json.dig('query','tokens','logintoken')
70
+ @login_cookie = remove_cookie_metadata(res.meta['set-cookie'])
71
+ puts(json) if debug
72
+ end
73
+
74
+ def get_csrf_cookie
75
+ puts("\nGetting csrf cookie using token #{login_token}") if debug
76
+ url = URI("#{baseurl}?action=login&lgname=#{username}&format=json")
77
+ req = Net::HTTP::Post.new(url, header.merge('cookie' => login_cookie, 'content-type' => 'application/x-www-form-urlencoded'))
78
+ req.set_form_data('lgpassword' => ENV['SECRET_WIKI_PASS'], 'lgtoken' => login_token)
79
+ http = Net::HTTP.new(url.host, url.port)
80
+ http.use_ssl = true
81
+ res=http.request(req)
82
+ @csrf_cookie = remove_cookie_metadata(res['set-cookie'])
83
+ File.write(csrf_cookie_file, @csrf_cookie)
84
+ puts(res.body) if debug
85
+ end
86
+
87
+ def get_csrf_token
88
+ puts("\nGetting csrf token using csrf cookies") if debug
89
+ url = URI("#{baseurl}?action=query&meta=tokens&format=json&type=csrf")
90
+ res = URI.open(url, header.merge('cookie' => csrf_cookie))
91
+ json = JSON.parse(res.read)
92
+ @csrf_token = json.dig('query','tokens','csrftoken')
93
+ puts(json) if debug
94
+ end
95
+
96
+ def build_query_string
97
+ params={}
98
+ params['action'] = 'edit'
99
+ params['format'] = Wikian::RESPONSE_FORMAT
100
+ params['title'] = input_file.sub(/\..*/,'')
101
+ wikitext = File.read(input_file)
102
+ if args.have?(%w(-a --append))
103
+ params['appendtext'] = wikitext
104
+ elsif args.have?(%w(-p --prepend))
105
+ params['prependtext'] = wikitext
106
+ else
107
+ # pass the wikitext in request body
108
+ @body_text = wikitext
109
+ end
110
+ if args.include?('-c')
111
+ params['captchaid'], params['captchaword'] = args[args.index('-c')+1].split(':')
112
+ end
113
+ if args.include?('-m')
114
+ params['summary'] = args[args.index('-m')+1]
115
+ end
116
+ @query = URI.encode_www_form(params)
117
+ end
118
+
119
+ def upload_article
120
+ puts("\nUploading the wiki article using csrf token #{csrf_token}") if debug
121
+ url = URI("#{baseurl}?#{query}")
122
+ req = Net::HTTP::Post.new(url, header.merge('cookie' => csrf_cookie, 'content-type' => 'application/x-www-form-urlencoded'))
123
+ http = Net::HTTP.new(url.host, url.port)
124
+ req_body = body_text.nil? ? {token: csrf_token} : {token: csrf_token, text: body_text}
125
+ req.set_form_data(req_body)
126
+ http = Net::HTTP.new(url.host, url.port)
127
+ http.use_ssl = true
128
+ res=http.request(req)
129
+ json = JSON.parse(res.body)
130
+ puts(json) if debug
131
+ if json.dig('error')
132
+ puts "An error occurred while uploding the file",
133
+ "Try pasing the '-r' option to remove '#{csrf_cookie_file}'",
134
+ "Or pass '-d' option for debugging"
135
+ else
136
+ puts "Article uploaded"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,40 @@
1
+ class Wikian
2
+ class WikianGetError < StandardError; end
3
+ class ArgumentRequiredError < WikianGetError; end
4
+
5
+ class Search < Subcommand
6
+ def initialize(args)
7
+ super
8
+
9
+ @output_file = yaml['api']['srsearch'].first
10
+
11
+ @params.merge!('format' => Wikian::RESPONSE_FORMAT)
12
+
13
+ @query = @params.to_query
14
+
15
+ @api_url = URI("https://#{yaml['meta']['site']}/w/api.php?#{query}")
16
+ rescue => e
17
+ puts "#{e.class} #{e.message} in #{__FILE__}"
18
+ exit
19
+ end
20
+
21
+ def template
22
+ <<~eos
23
+ # Get last 5 revisions of the Main Page.
24
+ meta:
25
+ site: en.wikipedia.org
26
+ headers:
27
+ user-agent: Wikian
28
+ api:
29
+ action:
30
+ - query
31
+ list:
32
+ - search
33
+ srsearch:
34
+ - Craig Noone
35
+ format:
36
+ - json
37
+ eos
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env -S ruby -W0
2
+ class Wikian
3
+ # class to be inherited by other Wikian classes
4
+ class Subcommand
5
+ attr_accessor :args, :res, :yaml, :query, :title, :api_url, :debug, :output_file
6
+
7
+ def initialize(args)
8
+ @args = args
9
+
10
+ if args.have?(%w(-t --template))
11
+ puts "Creating template #{Wikian::CONFIG_FILE}"
12
+ make_template
13
+ exit
14
+ end
15
+
16
+ @debug = (args & %w(-d --debug)).length > 0 ? true : false
17
+
18
+ @yaml=YAML.load(File.open(Wikian::CONFIG_FILE))
19
+
20
+ # some params like 'titles' can contain multiple entries joined by '|'. More info in Wikipedia API docs
21
+ @params = Hash[yaml['api'].keys.zip(yaml['api'].values.map{|arr| arr.join("|")})]
22
+ end
23
+
24
+ def make_template
25
+ if File.exist?(CONFIG_FILE)
26
+ puts "Overwrite existing '#{CONFIG_FILE}'? [yn]"
27
+ answer = STDIN.gets.chomp
28
+ (puts 'Bye'; exit) if answer != 'y'
29
+ end
30
+
31
+ File.open(CONFIG_FILE, 'w') do |f|
32
+ f.write template
33
+ end
34
+ exit
35
+ end
36
+
37
+ # HTTP response file name. Its extension depends on the 'content-type' header
38
+ def response_file
39
+ output_file + '.' + res['content-type'].split('/').last.sub(/;.*/,'')
40
+ end
41
+
42
+ # write response in to `response_file`
43
+ def write_response
44
+ STDERR.puts "Writing to #{response_file}"
45
+ File.open(response_file, 'w') do |f|
46
+ f.puts prettify(res.body)
47
+ end
48
+ end
49
+
50
+ def doit
51
+ puts api_url if debug
52
+
53
+ req = Net::HTTP::Get.new(api_url, yaml['meta']['headers'])
54
+
55
+ http = Net::HTTP.new(api_url.host, api_url.port)
56
+
57
+ http.use_ssl = true
58
+
59
+ @res=http.request(req)
60
+
61
+ write_response
62
+ rescue => e
63
+ puts "#{e.class} #{e.message} in #{__FILE__}"
64
+ exit
65
+ end
66
+
67
+ private
68
+
69
+ # if response is JSON prettify it, otherwise return it unchanged
70
+ def prettify(str)
71
+ res['content-type'].match?('json') ? JSON.pretty_generate(JSON.parse(str)) : str
72
+ end
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
- module Wikian
2
- VERSION = "0.1.0"
1
+ class Wikian
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,19 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wikian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergioro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-03 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Get and edit wikipedia articles
14
14
  email:
15
15
  - yo@sergioro.com
16
16
  executables:
17
+ - wi
17
18
  - wikian
18
19
  extensions: []
19
20
  extra_rdoc_files: []
@@ -21,13 +22,20 @@ files:
21
22
  - ".gitignore"
22
23
  - ".travis.yml"
23
24
  - Gemfile
25
+ - Gemfile.lock
24
26
  - LICENSE.txt
25
27
  - README.md
26
28
  - Rakefile
27
29
  - bin/console
28
30
  - bin/setup
31
+ - exe/wi
29
32
  - exe/wikian
33
+ - lib/.gitignore
30
34
  - lib/wikian.rb
35
+ - lib/wikian/get.rb
36
+ - lib/wikian/post.rb
37
+ - lib/wikian/search.rb
38
+ - lib/wikian/subcommand.rb
31
39
  - lib/wikian/version.rb
32
40
  - wikian.gemspec
33
41
  homepage: