Golang grep clone
I am trying to make a simple clone of the CMD line utility grep. I'm working on it just for fun and to practice GO. The thing is, I am trying to make it in a way that if several files are given at the command line, a different go routine is generated to speed up the process. The thing is, even so, standard grep still gets results faster by an amount of 60 % less processing time. ( Give or take)
Here it is the code for it.
Can you please provide some opinions about it?
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sync"
)
type result struct {
Filename string
Line string
LineNumber int
Error error
}
var strRex string
var filenames string
var regRex *regexp.Regexp
var wg sync.WaitGroup
var allResults result
var verbose = false
var recursive string
var recursiveFileList string
var fileFilter string
var rexfileFilter *regexp.Regexp
var inverseSearch bool
func init() {
var rexError error
flag.StringVar(&strRex, "r", "", "Regular expresion to match against the input files")
flag.BoolVar(&verbose, "v", false, "It sets verbose output (Basically showing filename and line number for each match)")
flag.BoolVar(&inverseSearch, "i", false, "It does what you might expect.. reverse the search")
flag.StringVar(&recursive, "R", "", "Recursively find all files starting from the current folder and apply the given search to them")
flag.StringVar(&fileFilter, "FF", "", "Filter to be applied to the filenames when used recursevily")
flag.Parse()
if strRex == "" {
fmt.Fprintf(os.Stderr, "The '-r' (regular expression flag is mandatory)n")
os.Exit(1)
}
regRex, rexError = regexp.Compile(strRex)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", strRex, rexError.Error())
os.Exit(2)
}
rexfileFilter, rexError = regexp.Compile(fileFilter)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", rexfileFilter, rexError.Error())
os.Exit(3)
}
if recursive != "" {
var err error
filenames, err = walkDir(recursive)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
}
} else {
filenames = flag.Args()
}
}
func main() {
stat, err := os.Stdin.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "There is an error reading from stdin : %s", err)
os.Exit(3)
}
if (stat.Mode() & os.ModeNamedPipe) != 0 {
grepStdin(os.Stdin, regRex)
} else {
chResults := make(chan *result, 4)
wg.Add(len(filenames))
for _, fn := range filenames {
go grep(fn, regRex, &wg, chResults)
}
go func(wait *sync.WaitGroup, ch chan<- *result) {
wg.Wait()
close(ch)
}(&wg, chResults)
for res := range chResults {
if verbose {
formatRes(res, 1)
} else {
formatRes(res, 2)
}
}
}
}
func grepStdin(ptr io.Reader, reg *regexp.Regexp) {
bf := bufio.NewScanner(ptr)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
formatRes(&result{
Line: line,
LineNumber: lineno,
Error: nil,
}, 3)
}
lineno++
}
}
func grep(file string, reg *regexp.Regexp, wait *sync.WaitGroup, ch chan<- *result) {
fd, err := os.Open(file)
if err != nil {
ch <- &result{
Filename: file,
Error: err,
}
}
bf := bufio.NewScanner(fd)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
ch <- &result{
Filename: file,
Line: line,
LineNumber: lineno,
Error: nil,
}
}
lineno++
}
wg.Done()
}
func formatRes(r *result, format int) {
if format == 1 {
if r.Error == nil {
fmt.Printf("%d - %s - %sn", r.LineNumber, r.Filename, r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 2 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 3 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%sn", r.Error)
}
}
}
func walkDir(path string) (string, error) {
list := make(string, 0, 50)
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if fileFilter != "" {
if rexfileFilter.Match(byte(filepath.Base(path))) {
list = append(list, path)
}
} else {
list = append(list, path)
}
return nil // Unreachable code
})
if err != nil {
return nil, err
}
return list, nil
}
go concurrency
New contributor
add a comment |
I am trying to make a simple clone of the CMD line utility grep. I'm working on it just for fun and to practice GO. The thing is, I am trying to make it in a way that if several files are given at the command line, a different go routine is generated to speed up the process. The thing is, even so, standard grep still gets results faster by an amount of 60 % less processing time. ( Give or take)
Here it is the code for it.
Can you please provide some opinions about it?
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sync"
)
type result struct {
Filename string
Line string
LineNumber int
Error error
}
var strRex string
var filenames string
var regRex *regexp.Regexp
var wg sync.WaitGroup
var allResults result
var verbose = false
var recursive string
var recursiveFileList string
var fileFilter string
var rexfileFilter *regexp.Regexp
var inverseSearch bool
func init() {
var rexError error
flag.StringVar(&strRex, "r", "", "Regular expresion to match against the input files")
flag.BoolVar(&verbose, "v", false, "It sets verbose output (Basically showing filename and line number for each match)")
flag.BoolVar(&inverseSearch, "i", false, "It does what you might expect.. reverse the search")
flag.StringVar(&recursive, "R", "", "Recursively find all files starting from the current folder and apply the given search to them")
flag.StringVar(&fileFilter, "FF", "", "Filter to be applied to the filenames when used recursevily")
flag.Parse()
if strRex == "" {
fmt.Fprintf(os.Stderr, "The '-r' (regular expression flag is mandatory)n")
os.Exit(1)
}
regRex, rexError = regexp.Compile(strRex)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", strRex, rexError.Error())
os.Exit(2)
}
rexfileFilter, rexError = regexp.Compile(fileFilter)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", rexfileFilter, rexError.Error())
os.Exit(3)
}
if recursive != "" {
var err error
filenames, err = walkDir(recursive)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
}
} else {
filenames = flag.Args()
}
}
func main() {
stat, err := os.Stdin.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "There is an error reading from stdin : %s", err)
os.Exit(3)
}
if (stat.Mode() & os.ModeNamedPipe) != 0 {
grepStdin(os.Stdin, regRex)
} else {
chResults := make(chan *result, 4)
wg.Add(len(filenames))
for _, fn := range filenames {
go grep(fn, regRex, &wg, chResults)
}
go func(wait *sync.WaitGroup, ch chan<- *result) {
wg.Wait()
close(ch)
}(&wg, chResults)
for res := range chResults {
if verbose {
formatRes(res, 1)
} else {
formatRes(res, 2)
}
}
}
}
func grepStdin(ptr io.Reader, reg *regexp.Regexp) {
bf := bufio.NewScanner(ptr)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
formatRes(&result{
Line: line,
LineNumber: lineno,
Error: nil,
}, 3)
}
lineno++
}
}
func grep(file string, reg *regexp.Regexp, wait *sync.WaitGroup, ch chan<- *result) {
fd, err := os.Open(file)
if err != nil {
ch <- &result{
Filename: file,
Error: err,
}
}
bf := bufio.NewScanner(fd)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
ch <- &result{
Filename: file,
Line: line,
LineNumber: lineno,
Error: nil,
}
}
lineno++
}
wg.Done()
}
func formatRes(r *result, format int) {
if format == 1 {
if r.Error == nil {
fmt.Printf("%d - %s - %sn", r.LineNumber, r.Filename, r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 2 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 3 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%sn", r.Error)
}
}
}
func walkDir(path string) (string, error) {
list := make(string, 0, 50)
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if fileFilter != "" {
if rexfileFilter.Match(byte(filepath.Base(path))) {
list = append(list, path)
}
} else {
list = append(list, path)
}
return nil // Unreachable code
})
if err != nil {
return nil, err
}
return list, nil
}
go concurrency
New contributor
add a comment |
I am trying to make a simple clone of the CMD line utility grep. I'm working on it just for fun and to practice GO. The thing is, I am trying to make it in a way that if several files are given at the command line, a different go routine is generated to speed up the process. The thing is, even so, standard grep still gets results faster by an amount of 60 % less processing time. ( Give or take)
Here it is the code for it.
Can you please provide some opinions about it?
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sync"
)
type result struct {
Filename string
Line string
LineNumber int
Error error
}
var strRex string
var filenames string
var regRex *regexp.Regexp
var wg sync.WaitGroup
var allResults result
var verbose = false
var recursive string
var recursiveFileList string
var fileFilter string
var rexfileFilter *regexp.Regexp
var inverseSearch bool
func init() {
var rexError error
flag.StringVar(&strRex, "r", "", "Regular expresion to match against the input files")
flag.BoolVar(&verbose, "v", false, "It sets verbose output (Basically showing filename and line number for each match)")
flag.BoolVar(&inverseSearch, "i", false, "It does what you might expect.. reverse the search")
flag.StringVar(&recursive, "R", "", "Recursively find all files starting from the current folder and apply the given search to them")
flag.StringVar(&fileFilter, "FF", "", "Filter to be applied to the filenames when used recursevily")
flag.Parse()
if strRex == "" {
fmt.Fprintf(os.Stderr, "The '-r' (regular expression flag is mandatory)n")
os.Exit(1)
}
regRex, rexError = regexp.Compile(strRex)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", strRex, rexError.Error())
os.Exit(2)
}
rexfileFilter, rexError = regexp.Compile(fileFilter)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", rexfileFilter, rexError.Error())
os.Exit(3)
}
if recursive != "" {
var err error
filenames, err = walkDir(recursive)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
}
} else {
filenames = flag.Args()
}
}
func main() {
stat, err := os.Stdin.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "There is an error reading from stdin : %s", err)
os.Exit(3)
}
if (stat.Mode() & os.ModeNamedPipe) != 0 {
grepStdin(os.Stdin, regRex)
} else {
chResults := make(chan *result, 4)
wg.Add(len(filenames))
for _, fn := range filenames {
go grep(fn, regRex, &wg, chResults)
}
go func(wait *sync.WaitGroup, ch chan<- *result) {
wg.Wait()
close(ch)
}(&wg, chResults)
for res := range chResults {
if verbose {
formatRes(res, 1)
} else {
formatRes(res, 2)
}
}
}
}
func grepStdin(ptr io.Reader, reg *regexp.Regexp) {
bf := bufio.NewScanner(ptr)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
formatRes(&result{
Line: line,
LineNumber: lineno,
Error: nil,
}, 3)
}
lineno++
}
}
func grep(file string, reg *regexp.Regexp, wait *sync.WaitGroup, ch chan<- *result) {
fd, err := os.Open(file)
if err != nil {
ch <- &result{
Filename: file,
Error: err,
}
}
bf := bufio.NewScanner(fd)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
ch <- &result{
Filename: file,
Line: line,
LineNumber: lineno,
Error: nil,
}
}
lineno++
}
wg.Done()
}
func formatRes(r *result, format int) {
if format == 1 {
if r.Error == nil {
fmt.Printf("%d - %s - %sn", r.LineNumber, r.Filename, r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 2 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 3 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%sn", r.Error)
}
}
}
func walkDir(path string) (string, error) {
list := make(string, 0, 50)
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if fileFilter != "" {
if rexfileFilter.Match(byte(filepath.Base(path))) {
list = append(list, path)
}
} else {
list = append(list, path)
}
return nil // Unreachable code
})
if err != nil {
return nil, err
}
return list, nil
}
go concurrency
New contributor
I am trying to make a simple clone of the CMD line utility grep. I'm working on it just for fun and to practice GO. The thing is, I am trying to make it in a way that if several files are given at the command line, a different go routine is generated to speed up the process. The thing is, even so, standard grep still gets results faster by an amount of 60 % less processing time. ( Give or take)
Here it is the code for it.
Can you please provide some opinions about it?
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sync"
)
type result struct {
Filename string
Line string
LineNumber int
Error error
}
var strRex string
var filenames string
var regRex *regexp.Regexp
var wg sync.WaitGroup
var allResults result
var verbose = false
var recursive string
var recursiveFileList string
var fileFilter string
var rexfileFilter *regexp.Regexp
var inverseSearch bool
func init() {
var rexError error
flag.StringVar(&strRex, "r", "", "Regular expresion to match against the input files")
flag.BoolVar(&verbose, "v", false, "It sets verbose output (Basically showing filename and line number for each match)")
flag.BoolVar(&inverseSearch, "i", false, "It does what you might expect.. reverse the search")
flag.StringVar(&recursive, "R", "", "Recursively find all files starting from the current folder and apply the given search to them")
flag.StringVar(&fileFilter, "FF", "", "Filter to be applied to the filenames when used recursevily")
flag.Parse()
if strRex == "" {
fmt.Fprintf(os.Stderr, "The '-r' (regular expression flag is mandatory)n")
os.Exit(1)
}
regRex, rexError = regexp.Compile(strRex)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", strRex, rexError.Error())
os.Exit(2)
}
rexfileFilter, rexError = regexp.Compile(fileFilter)
if rexError != nil {
fmt.Fprintf(os.Stderr, "Your regex '%s' cant compile. Error : %s", rexfileFilter, rexError.Error())
os.Exit(3)
}
if recursive != "" {
var err error
filenames, err = walkDir(recursive)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
}
} else {
filenames = flag.Args()
}
}
func main() {
stat, err := os.Stdin.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "There is an error reading from stdin : %s", err)
os.Exit(3)
}
if (stat.Mode() & os.ModeNamedPipe) != 0 {
grepStdin(os.Stdin, regRex)
} else {
chResults := make(chan *result, 4)
wg.Add(len(filenames))
for _, fn := range filenames {
go grep(fn, regRex, &wg, chResults)
}
go func(wait *sync.WaitGroup, ch chan<- *result) {
wg.Wait()
close(ch)
}(&wg, chResults)
for res := range chResults {
if verbose {
formatRes(res, 1)
} else {
formatRes(res, 2)
}
}
}
}
func grepStdin(ptr io.Reader, reg *regexp.Regexp) {
bf := bufio.NewScanner(ptr)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
formatRes(&result{
Line: line,
LineNumber: lineno,
Error: nil,
}, 3)
}
lineno++
}
}
func grep(file string, reg *regexp.Regexp, wait *sync.WaitGroup, ch chan<- *result) {
fd, err := os.Open(file)
if err != nil {
ch <- &result{
Filename: file,
Error: err,
}
}
bf := bufio.NewScanner(fd)
var lineno = 1
for bf.Scan() {
// There is no XOR in Golang, so you ahve to do this :
if line := bf.Text(); (reg.Match(byte(line)) && !inverseSearch) || (!reg.Match(byte(line)) && inverseSearch) {
ch <- &result{
Filename: file,
Line: line,
LineNumber: lineno,
Error: nil,
}
}
lineno++
}
wg.Done()
}
func formatRes(r *result, format int) {
if format == 1 {
if r.Error == nil {
fmt.Printf("%d - %s - %sn", r.LineNumber, r.Filename, r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 2 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%s - %s n", r.Filename, r.Error)
}
}
if format == 3 {
if r.Error == nil {
fmt.Printf("%sn", r.Line)
} else {
fmt.Fprintf(os.Stderr, "%sn", r.Error)
}
}
}
func walkDir(path string) (string, error) {
list := make(string, 0, 50)
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if fileFilter != "" {
if rexfileFilter.Match(byte(filepath.Base(path))) {
list = append(list, path)
}
} else {
list = append(list, path)
}
return nil // Unreachable code
})
if err != nil {
return nil, err
}
return list, nil
}
go concurrency
go concurrency
New contributor
New contributor
New contributor
asked 11 mins ago
Matias Barrios
1062
1062
New contributor
New contributor
add a comment |
add a comment |
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Matias Barrios is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210591%2fgolang-grep-clone%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Matias Barrios is a new contributor. Be nice, and check out our Code of Conduct.
Matias Barrios is a new contributor. Be nice, and check out our Code of Conduct.
Matias Barrios is a new contributor. Be nice, and check out our Code of Conduct.
Matias Barrios is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210591%2fgolang-grep-clone%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown