As developers, we often have to create command-line applications, which expose APIs, workflows, or data processing functionality in a way that is accessible to scripts and non-developers. Although a simple command-line application can be created by only using Array[String], such applications lack features that users take for granted, including flexible subcommands, options, arguments, validations and documentation pages.
In this presentation, Jorge Vásquez will show you how to easily build beautiful command-line applications that your users will love. Using ZIO CLI, your command-line applications will support all types of command-line parameters, perform both Pure and Impure validation, generate beautiful short and long documentation pages, and support auto-correction plus auto-completion.
Discover how you can have fun creating beautiful command-line apps that delight your users!
Wrapped in a single session, you'll find the concepts and techniques that convert the average Git practitioner into a master of the craft. We'll go from technical topics like "efficient conflict resolution" and "effective code cleanup," to the often-asked "how to handle project dependencies with Git" and "how to manage massive repositories." And much more.
Describes Outside-In development and Behvaiour Driven Development. Illustrates basic Cucumber usage within a Rails app and then goes over more advanced topics such as JS as web services.
This document discusses modifying the parser for the VeriFast verifier to better support verifying C code. VeriFast currently uses a Camlp4 parser that is a subset of C99, which can cause issues when verifying real C code that uses aspects beyond this subset. The document proposes patching VeriFast's parser to add support for parsing semicolons without declarations, hard tabs in string literals, the inline keyword, and operators in macros. The goal is to use the modified parser to verify code from the NetBSD kernel as a test case.
Flowr offers a scatter-gather approach, and works by submitting a web of jobs to the computing cluster using dependencies. It requires two simple data.frames as inputs and supports platforms like LSF, SGE, Torque and SLURM.
The document discusses refactoring Terraform configuration files to improve their design. It provides an example of refactoring a "supermarket-terraform" configuration that originally defined AWS resources across multiple files. The refactoring consolidates the configuration into a single file and adds testing using Test Kitchen. It emphasizes starting small by adding tests incrementally and not making changes without tests to avoid introducing errors.
This document provides an overview of Linux command line essentials including shell script basics, variables, operators, loops, and functions. It covers topics such as the difference between CLI and GUI interfaces, what a kernel and shell are, defining variables and variable types, arithmetic, relational, boolean, and file test operators, while, for, and until loops, and creating reusable functions. The document is from edureka.co and serves as an introduction to common Linux shell scripting concepts.
Deploying Next Gen Systems with Zero DowntimeTwilio Inc
The document discusses deploying next-generation systems at Twilio while ensuring zero downtime and zero regressions. It describes the challenges of replacing two core systems while maintaining high availability and horizontal scalability without losing any data. It outlines techniques used such as running tests against both systems, canary deployments, shadow mode with double bookkeeping for rollbacks, and gradual rollouts. The goal is reliable online upgrades of critical systems processing millions of transactions.
Wrapped in a single session, you'll find the concepts and techniques that convert the average Git practitioner into a master of the craft. We'll go from technical topics like "efficient conflict resolution" and "effective code cleanup," to the often-asked "how to handle project dependencies with Git" and "how to manage massive repositories." And much more.
Describes Outside-In development and Behvaiour Driven Development. Illustrates basic Cucumber usage within a Rails app and then goes over more advanced topics such as JS as web services.
This document discusses modifying the parser for the VeriFast verifier to better support verifying C code. VeriFast currently uses a Camlp4 parser that is a subset of C99, which can cause issues when verifying real C code that uses aspects beyond this subset. The document proposes patching VeriFast's parser to add support for parsing semicolons without declarations, hard tabs in string literals, the inline keyword, and operators in macros. The goal is to use the modified parser to verify code from the NetBSD kernel as a test case.
Flowr offers a scatter-gather approach, and works by submitting a web of jobs to the computing cluster using dependencies. It requires two simple data.frames as inputs and supports platforms like LSF, SGE, Torque and SLURM.
The document discusses refactoring Terraform configuration files to improve their design. It provides an example of refactoring a "supermarket-terraform" configuration that originally defined AWS resources across multiple files. The refactoring consolidates the configuration into a single file and adds testing using Test Kitchen. It emphasizes starting small by adding tests incrementally and not making changes without tests to avoid introducing errors.
This document provides an overview of Linux command line essentials including shell script basics, variables, operators, loops, and functions. It covers topics such as the difference between CLI and GUI interfaces, what a kernel and shell are, defining variables and variable types, arithmetic, relational, boolean, and file test operators, while, for, and until loops, and creating reusable functions. The document is from edureka.co and serves as an introduction to common Linux shell scripting concepts.
Deploying Next Gen Systems with Zero DowntimeTwilio Inc
The document discusses deploying next-generation systems at Twilio while ensuring zero downtime and zero regressions. It describes the challenges of replacing two core systems while maintaining high availability and horizontal scalability without losing any data. It outlines techniques used such as running tests against both systems, canary deployments, shadow mode with double bookkeeping for rollbacks, and gradual rollouts. The goal is reliable online upgrades of critical systems processing millions of transactions.
The document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim's modes, motions, text objects, syntax highlighting and mappings to more efficiently edit Perl code. Specifically, it shows how Normal mode motions, Insert mode, Visual mode, syntax files for custom file types, and mappings can help avoid typos and speed up common tasks like indentation. It estimates that these Vim features could save a developer over 16 hours per year compared to a basic editor.
Test driven infrastructure development (2 - puppetconf 2013 edition)Tomas Doran
The document discusses test driven infrastructure development. It describes issues with the current state where infrastructure changes are not repeatable and difficult to test. The speaker proposes modeling infrastructure as code where environments are defined programmatically and configuration is generated externally rather than defined directly in puppet code. This allows for entire environments to be provisioned on demand and tested in an automated and repeatable way. Key benefits include high availability, ability to test all infrastructure changes, fully repeatable environments, high confidence in changes, and continuous integration/deployment of infrastructure.
The document discusses various approaches for exposing instance variables from controllers to views in Ruby on Rails applications. It compares the pros and cons of using helper_method, decent_exposure, and obviews for this purpose. Examples are provided showing how to expose a post object from a PostsController to views using these different approaches. The discussion concludes by noting that the obviews gem for exposing instance variables is being released on RubyGems.
The document discusses various features of the Vim text editor, including modes (normal, insert, visual), text objects, syntax highlighting, encoding, key mappings, tab pages, and folds. It provides examples of motions and operations in normal mode, editing text in insert mode, selecting regions in visual mode, and syntax definitions. It also covers setting the encoding, defining common key mappings, using tab pages, and folding code with different fold methods.
The document discusses various features of the Vim text editor, including modes (normal, insert, visual), text objects, syntax highlighting, encoding, key mappings, tab pages, and folds. It provides examples of motions and operations in normal mode, editing text in insert mode, selecting regions in visual mode, and syntax definitions. It also covers setting the encoding, defining common key mappings, using tab pages, and folding code with different fold methods.
TypeScript와 Flow: 자바스크립트 개발에 정적 타이핑 도입하기Heejong Ahn
- The document discusses TypeScript and Flow, two type systems for JavaScript. It provides information on their history, design goals around soundness vs productivity, usage statistics, and comparisons of their type systems and tooling support.
- Key differences noted are that Flow focuses on soundness while TypeScript balances correctness and productivity, and that TypeScript has significantly more resources and adoption based on metrics like StackOverflow questions, GitHub stars, and npm downloads.
The document provides an outline on installing and configuring Git, introduces common Git concepts and commands, discusses various Git workflows and hosting options on GitHub and Bitbucket, and includes examples of using Git in case studies. It covers topics such as setting up a local and global Git configuration, interacting with the staging area and working directory, branching and merging, and resolving conflicts. The document is intended to teach users the basics of using the popular version control system Git.
Boîte à outils d'investigation des soucis de mémoireJean Bisutti
The document discusses various tools and techniques for investigating memory issues in Java applications, including:
- Using JDK commands like jcmd, jps, jstat, and jmap to analyze heap dumps and GC logs
- Setting JVM parameters like -XX:+PrintGCDetails to log GC information
- Using GC log analyzers and profilers like GCViewer and Java Mission Control
- Dumping the heap with jmap or -XX:+HeapDumpOnOutOfMemoryError
- Analyzing heap dumps with the Memory Analyzer Tool to find memory leaks
The overall focus is on the debugging tools and approaches available for memory and GC troubleshooting in Java.
The document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim's modes, motions, text objects, syntax highlighting and mappings to more efficiently edit and navigate Perl code. The Normal mode, Insert mode, and Visual mode are demonstrated along with motions, text objects, folding and syntax files that are helpful for Perl. Key mappings are also shown that can make Perl editing more productive in Vim.
The document provides an overview of the .NET framework and C# programming. It discusses how programs worked before .NET, the components of the .NET framework, and versions of the .NET framework. It then covers various aspects of C# programming like writing C# programs, variables and expressions, flow control, more on variables, and debugging and error handling.
This document provides an overview of PHP basics, including variables, data types, and conditional statements. It discusses the different variable scopes in PHP like local, global, static variables. It also covers data types like integers, strings, arrays and how to assign values to variables. The document explains single and multi-dimensional arrays and different operators in PHP. Finally, it summarizes the different conditional statements like if, if-else, if-elseif-else and switch statements that allow executing code conditionally.
Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019confluent
Cloud migration: it's practically a rite of passage for anyone who's built infrastructure on bare metal. When we migrated our 5-year-old Kafka deployment from the datacenter to GCP, we were faced with the task of making our highly mutable server infrastructure more cloud-friendly. This led to a surprising decision: we chose to run our Kafka cluster on Kubernetes. I'll share war stories from our Kafka migration journey, explain why we chose Kubernetes over arguably simpler options like GCP VMs, and present the lessons we learned while making our way toward a stable and self-healing Kubernetes deployment. I'll also go through some improvements in the more recent Kafka releases that make upgrades crucial for any Kafka deployment on immutable and ephemeral infrastructure. You'll learn what happens when you try to run one complex distributed system on top of another, and come away with some handy tricks for automating cloud cluster management, plus some migration pitfalls to avoid. And if you're not sure whether running Kafka on Kubernetes is right for you, our experiences should provide some extra data points that you can use as you make that decision.
Your own (little) gem: building an online business with RubyLindsay Holmwood
The document provides instructions for building an online business using Ruby on Rails. It recommends starting with conservative financial estimates, limiting initial features to 1-2 core functions, and using specific Ruby gems like Merb and DataMapper for rapid web development. It then outlines steps for bootstrapping a sample Rails application, writing model and request specs to test it, and implementing additional features like sorting pub data and using Haml templates.
Cucumber lets software teams describe how software should behave in plain text using a business-readable domain-specific language. This text serves as documentation, automated tests, and aids development by being written as features and scenarios. Cucumber uses plugins like Webrat and Nokogiri to run scenarios and check behavior. It generates step definitions to map scenarios to code.
Dave Liddament - международный спикер и разработчик в Lamp Bristol. Мы пригласили его выступить у нас на конференции 8 декабря с темой «Effective Code Review».
⠀
There are so many benefits from code review; lower development costs, increased code quality, quick up-skilling and on-boarding of team members. Despite these benefits many teams don’t have code review as part of their process at all. Others don’t get the gains they should from it.
⠀
This talk first outlines the business case for code review. It then explores how to make code review effective, specifically looking at:
⠀
• Expectations of what can be achieved with code review.
• What should be covered by code review (including example code)
• What should not be covered by code review
• How to write code that makes code review easy
• What makes good code review comments
• What makes good responses to code review comments
⠀
Finally to wrap up you'll be shown how to enable a code review with Github. Spoiler alert: It can be done it under 5 mins!So if you are on a team that isn’t using code review, or isn’t using it effectively then you should be at this talk.
CODEiD – это всеукраинское сообщество PHP-разработчиков. Наша цель — создать сильное сообщество всех, кто увлечен PHP-разработкой, и принимать в нашем уютном приморском городе коллег со всей Украины и мира.
This document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim motions and modes like Normal mode, Insert mode, and Visual mode to efficiently edit code. It also covers Vim features like syntax highlighting, custom syntax files, key mappings, and text objects that are useful for Perl. The document advocates that Vim is a powerful editor rather than an IDE and highlights how it can save significant time compared to less efficient editing methods.
What we Learned Implementing Puppet at BackstopPuppet
"What We Learned Implementing Puppet at Backstop" by Bill Weiss at Puppet Camp Chicago 2013. Learn about upcoming Puppet Camps at http://puppetlabs.com/community/puppet-camp/
Sprockets is an easy solution to managing large JavaScript codebases by letting you structure it, bundle it with related assets, and consolidate it as one single file, with pre-baked command-line tooling, CGI front and Rails plugin. It's a framework-agnostic open-source solution that makes for great serving performance while helping you structure and manage your codebase better.
Programación Funcional 101 con Scala y ZIO 2.0Jorge Vásquez
El documento presenta una agenda para una presentación sobre conceptos básicos de programación funcional con Scala y ZIO. La agenda incluye secciones sobre conceptos básicos de programación funcional, conceptos básicos de ZIO y un ejemplo práctico de un juego de tres en línea.
Some parts of our applications don't need to be asynchronous or interact with the outside world: it's enough that they are stateful, possibly with the ability to handle failure, context, and logging. Although you can use ZIO 2 or monad transformers for this task, both come with drawbacks. In this presentation, Jorge Vásquez will introduce you to ZPure, a data type from ZIO Prelude, which lets you scale back on the power of ZIO 2, but with the same high-performance, type-inference, and ergonomics you expect from ZIO 2 libraries.
More Related Content
Similar to Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!
The document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim's modes, motions, text objects, syntax highlighting and mappings to more efficiently edit Perl code. Specifically, it shows how Normal mode motions, Insert mode, Visual mode, syntax files for custom file types, and mappings can help avoid typos and speed up common tasks like indentation. It estimates that these Vim features could save a developer over 16 hours per year compared to a basic editor.
Test driven infrastructure development (2 - puppetconf 2013 edition)Tomas Doran
The document discusses test driven infrastructure development. It describes issues with the current state where infrastructure changes are not repeatable and difficult to test. The speaker proposes modeling infrastructure as code where environments are defined programmatically and configuration is generated externally rather than defined directly in puppet code. This allows for entire environments to be provisioned on demand and tested in an automated and repeatable way. Key benefits include high availability, ability to test all infrastructure changes, fully repeatable environments, high confidence in changes, and continuous integration/deployment of infrastructure.
The document discusses various approaches for exposing instance variables from controllers to views in Ruby on Rails applications. It compares the pros and cons of using helper_method, decent_exposure, and obviews for this purpose. Examples are provided showing how to expose a post object from a PostsController to views using these different approaches. The discussion concludes by noting that the obviews gem for exposing instance variables is being released on RubyGems.
The document discusses various features of the Vim text editor, including modes (normal, insert, visual), text objects, syntax highlighting, encoding, key mappings, tab pages, and folds. It provides examples of motions and operations in normal mode, editing text in insert mode, selecting regions in visual mode, and syntax definitions. It also covers setting the encoding, defining common key mappings, using tab pages, and folding code with different fold methods.
The document discusses various features of the Vim text editor, including modes (normal, insert, visual), text objects, syntax highlighting, encoding, key mappings, tab pages, and folds. It provides examples of motions and operations in normal mode, editing text in insert mode, selecting regions in visual mode, and syntax definitions. It also covers setting the encoding, defining common key mappings, using tab pages, and folding code with different fold methods.
TypeScript와 Flow: 자바스크립트 개발에 정적 타이핑 도입하기Heejong Ahn
- The document discusses TypeScript and Flow, two type systems for JavaScript. It provides information on their history, design goals around soundness vs productivity, usage statistics, and comparisons of their type systems and tooling support.
- Key differences noted are that Flow focuses on soundness while TypeScript balances correctness and productivity, and that TypeScript has significantly more resources and adoption based on metrics like StackOverflow questions, GitHub stars, and npm downloads.
The document provides an outline on installing and configuring Git, introduces common Git concepts and commands, discusses various Git workflows and hosting options on GitHub and Bitbucket, and includes examples of using Git in case studies. It covers topics such as setting up a local and global Git configuration, interacting with the staging area and working directory, branching and merging, and resolving conflicts. The document is intended to teach users the basics of using the popular version control system Git.
Boîte à outils d'investigation des soucis de mémoireJean Bisutti
The document discusses various tools and techniques for investigating memory issues in Java applications, including:
- Using JDK commands like jcmd, jps, jstat, and jmap to analyze heap dumps and GC logs
- Setting JVM parameters like -XX:+PrintGCDetails to log GC information
- Using GC log analyzers and profilers like GCViewer and Java Mission Control
- Dumping the heap with jmap or -XX:+HeapDumpOnOutOfMemoryError
- Analyzing heap dumps with the Memory Analyzer Tool to find memory leaks
The overall focus is on the debugging tools and approaches available for memory and GC troubleshooting in Java.
The document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim's modes, motions, text objects, syntax highlighting and mappings to more efficiently edit and navigate Perl code. The Normal mode, Insert mode, and Visual mode are demonstrated along with motions, text objects, folding and syntax files that are helpful for Perl. Key mappings are also shown that can make Perl editing more productive in Vim.
The document provides an overview of the .NET framework and C# programming. It discusses how programs worked before .NET, the components of the .NET framework, and versions of the .NET framework. It then covers various aspects of C# programming like writing C# programs, variables and expressions, flow control, more on variables, and debugging and error handling.
This document provides an overview of PHP basics, including variables, data types, and conditional statements. It discusses the different variable scopes in PHP like local, global, static variables. It also covers data types like integers, strings, arrays and how to assign values to variables. The document explains single and multi-dimensional arrays and different operators in PHP. Finally, it summarizes the different conditional statements like if, if-else, if-elseif-else and switch statements that allow executing code conditionally.
Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019confluent
Cloud migration: it's practically a rite of passage for anyone who's built infrastructure on bare metal. When we migrated our 5-year-old Kafka deployment from the datacenter to GCP, we were faced with the task of making our highly mutable server infrastructure more cloud-friendly. This led to a surprising decision: we chose to run our Kafka cluster on Kubernetes. I'll share war stories from our Kafka migration journey, explain why we chose Kubernetes over arguably simpler options like GCP VMs, and present the lessons we learned while making our way toward a stable and self-healing Kubernetes deployment. I'll also go through some improvements in the more recent Kafka releases that make upgrades crucial for any Kafka deployment on immutable and ephemeral infrastructure. You'll learn what happens when you try to run one complex distributed system on top of another, and come away with some handy tricks for automating cloud cluster management, plus some migration pitfalls to avoid. And if you're not sure whether running Kafka on Kubernetes is right for you, our experiences should provide some extra data points that you can use as you make that decision.
Your own (little) gem: building an online business with RubyLindsay Holmwood
The document provides instructions for building an online business using Ruby on Rails. It recommends starting with conservative financial estimates, limiting initial features to 1-2 core functions, and using specific Ruby gems like Merb and DataMapper for rapid web development. It then outlines steps for bootstrapping a sample Rails application, writing model and request specs to test it, and implementing additional features like sorting pub data and using Haml templates.
Cucumber lets software teams describe how software should behave in plain text using a business-readable domain-specific language. This text serves as documentation, automated tests, and aids development by being written as features and scenarios. Cucumber uses plugins like Webrat and Nokogiri to run scenarios and check behavior. It generates step definitions to map scenarios to code.
Dave Liddament - международный спикер и разработчик в Lamp Bristol. Мы пригласили его выступить у нас на конференции 8 декабря с темой «Effective Code Review».
⠀
There are so many benefits from code review; lower development costs, increased code quality, quick up-skilling and on-boarding of team members. Despite these benefits many teams don’t have code review as part of their process at all. Others don’t get the gains they should from it.
⠀
This talk first outlines the business case for code review. It then explores how to make code review effective, specifically looking at:
⠀
• Expectations of what can be achieved with code review.
• What should be covered by code review (including example code)
• What should not be covered by code review
• How to write code that makes code review easy
• What makes good code review comments
• What makes good responses to code review comments
⠀
Finally to wrap up you'll be shown how to enable a code review with Github. Spoiler alert: It can be done it under 5 mins!So if you are on a team that isn’t using code review, or isn’t using it effectively then you should be at this talk.
CODEiD – это всеукраинское сообщество PHP-разработчиков. Наша цель — создать сильное сообщество всех, кто увлечен PHP-разработкой, и принимать в нашем уютном приморском городе коллег со всей Украины и мира.
This document discusses how Vim can improve productivity for Perl coding. It provides examples of using Vim motions and modes like Normal mode, Insert mode, and Visual mode to efficiently edit code. It also covers Vim features like syntax highlighting, custom syntax files, key mappings, and text objects that are useful for Perl. The document advocates that Vim is a powerful editor rather than an IDE and highlights how it can save significant time compared to less efficient editing methods.
What we Learned Implementing Puppet at BackstopPuppet
"What We Learned Implementing Puppet at Backstop" by Bill Weiss at Puppet Camp Chicago 2013. Learn about upcoming Puppet Camps at http://puppetlabs.com/community/puppet-camp/
Sprockets is an easy solution to managing large JavaScript codebases by letting you structure it, bundle it with related assets, and consolidate it as one single file, with pre-baked command-line tooling, CGI front and Rails plugin. It's a framework-agnostic open-source solution that makes for great serving performance while helping you structure and manage your codebase better.
Programación Funcional 101 con Scala y ZIO 2.0Jorge Vásquez
El documento presenta una agenda para una presentación sobre conceptos básicos de programación funcional con Scala y ZIO. La agenda incluye secciones sobre conceptos básicos de programación funcional, conceptos básicos de ZIO y un ejemplo práctico de un juego de tres en línea.
Some parts of our applications don't need to be asynchronous or interact with the outside world: it's enough that they are stateful, possibly with the ability to handle failure, context, and logging. Although you can use ZIO 2 or monad transformers for this task, both come with drawbacks. In this presentation, Jorge Vásquez will introduce you to ZPure, a data type from ZIO Prelude, which lets you scale back on the power of ZIO 2, but with the same high-performance, type-inference, and ergonomics you expect from ZIO 2 libraries.
Be Smart, Constrain Your Types to Free Your Brain!Jorge Vásquez
The document discusses different approaches to representing data with smart types in Scala to validate data at compile time. It describes using smart constructors with Either types, the Newtype library, and the Refined library to define types for a Person case class that validate fields like names, emails and addresses. Using these approaches helps prevent invalid data from being stored by catching errors at compile time rather than runtime.
Functional Programming 101 with Scala and ZIO @FunctionalWorldJorge Vásquez
This document provides an overview of functional programming concepts and the ZIO library. It discusses the characteristics of pure functions, including being total, deterministic, and having no side effects. It contrasts functional programming with object-oriented programming. Benefits of the functional paradigm discussed include local reasoning, referential transparency, conciseness, easier testing, and support for parallel programming. The document then introduces the ZIO library, describing how it allows building resilient, asynchronous, and efficient applications using functional principles. It outlines ZIO's data type and how it represents effects.
The document discusses using smart constructors and opaque types to improve data modeling in Scala. It describes problems with representing all data as simple types, like case classes, which can allow invalid data. The presentation introduces using smart constructors with opaque types to define domain-specific types for name, email, address, etc. and validate values when constructing instances of these types and the Person case class. This ensures invalid data cannot be stored.
Exploring type level programming in ScalaJorge Vásquez
In this introduction to type-level programming in Scala, we are going to discuss how we can leverage the full power of the type system to verify domain properties of an application at compile-time, instead of doing runtime verifications at the value-level
The Terror-Free Guide to Introducing Functional Scala at WorkJorge Vásquez
Too often, our applications are dominated by boilerplate that's not fun to write or test, and that makes our business logic complicated. In object-oriented programming, classes and interfaces help us with abstraction to reduce boilerplate. But, in functional programming, we use type classes.
Historically, type classes in functional programming have been very complex and confusing, partially because they import ideas from Haskell that don't make sense in Scala, and partially because of their esoteric origins in category theory.
In this presentation, Jorge Vásquez presents a new library called ZIO Prelude, which offers a distinctly Scala take on Functional Abstractions, and you will learn how you can eliminate common types of boilerplate by using it.
Come see how you can improve your happiness and productivity with a new take on what it means to do functional programming in Scala!
Exploring ZIO Prelude: The game changer for typeclasses in ScalaJorge Vásquez
The document discusses ZIO Prelude, a Scala library that provides typeclasses. It motivates ZIO Prelude by explaining limitations of Scalaz in Scala compared to Haskell. It then provides an overview and tour of capabilities in ZIO Prelude, including validating data with Validation to accumulate all errors, combining nested data structures by summing stats, and executing pure computations. The document uses examples to demonstrate how ZIO Prelude provides a more Scala-centric approach to functional programming compared to porting Haskell concepts.
Introduction to programming with ZIO functional effectsJorge Vásquez
This document provides an introduction to functional programming concepts and the ZIO library. It discusses programming with pure functions, handling side effects through effect types, and the ZIO data type for describing side effects in a type-safe way. It then introduces some common ZIO type aliases like Task, UIO, RIO, IO and URIO. The document concludes by mentioning a live coding example of building a Hangman game using ZIO.
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemPeter Muessig
Learn about the latest innovations in and around OpenUI5/SAPUI5: UI5 Tooling, UI5 linter, UI5 Web Components, Web Components Integration, UI5 2.x, UI5 GenAI.
Recording:
https://www.youtube.com/live/MSdGLG2zLy8?si=INxBHTqkwHhxV5Ta&t=0
Top 9 Trends in Cybersecurity for 2024.pptxdevvsandy
Security and risk management (SRM) leaders face disruptions on technological, organizational, and human fronts. Preparation and pragmatic execution are key for dealing with these disruptions and providing the right cybersecurity program.
Hand Rolled Applicative User ValidationCode KataPhilip Schwarz
Could you use a simple piece of Scala validation code (granted, a very simplistic one too!) that you can rewrite, now and again, to refresh your basic understanding of Applicative operators <*>, <*, *>?
The goal is not to write perfect code showcasing validation, but rather, to provide a small, rough-and ready exercise to reinforce your muscle-memory.
Despite its grandiose-sounding title, this deck consists of just three slides showing the Scala 3 code to be rewritten whenever the details of the operators begin to fade away.
The code is my rough and ready translation of a Haskell user-validation program found in a book called Finding Success (and Failure) in Haskell - Fall in love with applicative functors.
WWDC 2024 Keynote Review: For CocoaCoders AustinPatrick Weigel
Overview of WWDC 2024 Keynote Address.
Covers: Apple Intelligence, iOS18, macOS Sequoia, iPadOS, watchOS, visionOS, and Apple TV+.
Understandable dialogue on Apple TV+
On-device app controlling AI.
Access to ChatGPT with a guest appearance by Chief Data Thief Sam Altman!
App Locking! iPhone Mirroring! And a Calculator!!
Measures in SQL (SIGMOD 2024, Santiago, Chile)Julian Hyde
SQL has attained widespread adoption, but Business Intelligence tools still use their own higher level languages based upon a multidimensional paradigm. Composable calculations are what is missing from SQL, and we propose a new kind of column, called a measure, that attaches a calculation to a table. Like regular tables, tables with measures are composable and closed when used in queries.
SQL-with-measures has the power, conciseness and reusability of multidimensional languages but retains SQL semantics. Measure invocations can be expanded in place to simple, clear SQL.
To define the evaluation semantics for measures, we introduce context-sensitive expressions (a way to evaluate multidimensional expressions that is consistent with existing SQL semantics), a concept called evaluation context, and several operations for setting and modifying the evaluation context.
A talk at SIGMOD, June 9–15, 2024, Santiago, Chile
Authors: Julian Hyde (Google) and John Fremlin (Google)
https://doi.org/10.1145/3626246.3653374
Flutter is a popular open source, cross-platform framework developed by Google. In this webinar we'll explore Flutter and its architecture, delve into the Flutter Embedder and Flutter’s Dart language, discover how to leverage Flutter for embedded device development, learn about Automotive Grade Linux (AGL) and its consortium and understand the rationale behind AGL's choice of Flutter for next-gen IVI systems. Don’t miss this opportunity to discover whether Flutter is right for your project.
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Łukasz Chruściel
No one wants their application to drag like a car stuck in the slow lane! Yet it’s all too common to encounter bumpy, pothole-filled solutions that slow the speed of any application. Symfony apps are not an exception.
In this talk, I will take you for a spin around the performance racetrack. We’ll explore common pitfalls - those hidden potholes on your application that can cause unexpected slowdowns. Learn how to spot these performance bumps early, and more importantly, how to navigate around them to keep your application running at top speed.
We will focus in particular on tuning your engine at the application level, making the right adjustments to ensure that your system responds like a well-oiled, high-performance race car.
Mobile App Development Company In Noida | Drona InfotechDrona Infotech
Drona Infotech is a premier mobile app development company in Noida, providing cutting-edge solutions for businesses.
Visit Us For : https://www.dronainfotech.com/mobile-application-development/
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfVALiNTRY360
Salesforce Healthcare CRM, implemented by VALiNTRY360, revolutionizes patient management by enhancing patient engagement, streamlining administrative processes, and improving care coordination. Its advanced analytics, robust security, and seamless integration with telehealth services ensure that healthcare providers can deliver personalized, efficient, and secure patient care. By automating routine tasks and providing actionable insights, Salesforce Healthcare CRM enables healthcare providers to focus on delivering high-quality care, leading to better patient outcomes and higher satisfaction. VALiNTRY360's expertise ensures a tailored solution that meets the unique needs of any healthcare practice, from small clinics to large hospital systems.
For more info visit us https://valintry360.com/solutions/health-life-sciences
When it is all about ERP solutions, companies typically meet their needs with common ERP solutions like SAP, Oracle, and Microsoft Dynamics. These big players have demonstrated that ERP systems can be either simple or highly comprehensive. This remains true today, but there are new factors to consider, including a promising new contender in the market that’s Odoo. This blog compares Odoo ERP with traditional ERP systems and explains why many companies now see Odoo ERP as the best choice.
What are ERP Systems?
An ERP, or Enterprise Resource Planning, system provides your company with valuable information to help you make better decisions and boost your ROI. You should choose an ERP system based on your company’s specific needs. For instance, if you run a manufacturing or retail business, you will need an ERP system that efficiently manages inventory. A consulting firm, on the other hand, would benefit from an ERP system that enhances daily operations. Similarly, eCommerce stores would select an ERP system tailored to their needs.
Because different businesses have different requirements, ERP system functionalities can vary. Among the various ERP systems available, Odoo ERP is considered one of the best in the ERp market with more than 12 million global users today.
Odoo is an open-source ERP system initially designed for small to medium-sized businesses but now suitable for a wide range of companies. Odoo offers a scalable and configurable point-of-sale management solution and allows you to create customised modules for specific industries. Odoo is gaining more popularity because it is built in a way that allows easy customisation, has a user-friendly interface, and is affordable. Here, you will cover the main differences and get to know why Odoo is gaining attention despite the many other ERP systems available in the market.
4. Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
✓ Visit https://scalac.io/services/
training/
5. Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
✓ Visit https://scalac.io/services/
training/
✓ Or, if you are onsite at the
conference you can visit our
booth!
8. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
9. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
10. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
11. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
12. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
13. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
✓AWS CLI
14. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
✓AWS CLI
✓Git
28. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
29. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
30. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
31. We need to handle Flags!
$ git commit –-message "My first commit" --verbose
32. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
33. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
34. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
35. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
36. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
37. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
38. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
63. Impure Validation
We need to verify a connection between an option/
argument and the Real World
✓ Does the given file actually exist?
64. Impure Validation
We need to verify a connection between an option/
argument and the Real World
✓ Does the given file actually exist?
✓ Is the given URL valid?
79. What is ZIO CLI?
✓ It's a library that allows you to rapidly build Powerful
Command-line Applications powered by ZIO
80. What is ZIO CLI?
✓ It's a library that allows you to rapidly build Powerful
Command-line Applications powered by ZIO
✓ https://github.com/zio/zio-cli
85. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
86. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
87. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
88. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
✓ Replicate the given CSV file into several
others with the given separators
89. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
✓ Replicate the given CSV file into several
others with the given separators
✓ Convert to JSON, allowing pretty
printing
91. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
92. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
93. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
94. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
95. Define a Pure Data-
Structure that models
inputs to all possible
Subcommands
96. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
97. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
98. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
99. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
100. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
101. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
102. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
103. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
104. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
105. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
106. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
107. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
109. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
110. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
111. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
112. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
113. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
114. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
115. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
116. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
117. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
118. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
119. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
120. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
121. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
122. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
123. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
124. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
125. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
126. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
127. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
128. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
129. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
130. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
131. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
132. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
133. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
134. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
135. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
136. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
137. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
138. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
139. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
140. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
141. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
142. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
143. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
144. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newIndexOption = Options.integer("new-index").alias("i") ?? "New column index." // Pure Validation in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the move-column Command
val moveColumn =
Command(name = "move-column", options = columnOption ++ newIndexOption ++ separatorOption, args = inputCsvArg)
.withHelp("Move a column of the given CSV file.")
.map { case ((column, newIndex, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.MoveColumn(column, newIndex))
}
145. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newIndexOption = Options.integer("new-index").alias("i") ?? "New column index." // Pure Validation in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the move-column Command
val moveColumn =
Command(name = "move-column", options = columnOption ++ newIndexOption ++ separatorOption, args = inputCsvArg)
.withHelp("Move a column of the given CSV file.")
.map { case ((column, newIndex, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.MoveColumn(column, newIndex))
}
146. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
val newSeparatorOption = Options.text("new-separator") ?? "New separator string." // You don't always need to define an alias!
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the change-separator Command
val changeSeparator =
Command(name = "change-separator", options = separatorOption ++ newSeparatorOption, args = inputCsvArg)
.withHelp("Change the separator string of the given CSV file.")
.map { case ((separator, newSeparator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.ChangeSeparator(newSeparator))
}
147. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
val newSeparatorOption = Options.text("new-separator") ?? "New separator string." // You don't always need to define an alias!
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the change-separator Command
val changeSeparator =
Command(name = "change-separator", options = separatorOption ++ newSeparatorOption, args = inputCsvArg)
.withHelp("Change the separator string of the given CSV file.")
.map { case ((separator, newSeparator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.ChangeSeparator(newSeparator))
}
148. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
149. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
150. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
151. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
152. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
153. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
174. Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
✓Generates a GraalVM native image for you
175. Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
✓Generates a GraalVM native image for you
✓Installs this as an executable on your machine
216. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
217. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
✓ However, writing production-grade CLI apps is hard
218. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
✓ However, writing production-grade CLI apps is hard
✓ Actually, it doesn't have to be hard, not with ZIO CLI!!!